The important things for writing modular code in a big codebase is defining clear interfaces and making "black box" modules. An important thing for this is abstract data types but, unlike what many people seem to say, those are not exclusive to OOP - you can also have abstract datatypes in procedural or functional languages.
The core concept of OOP is adding dynamic method-based dispatching to your abstract data types but I would argue that while this is useful a lot of times, its not really fundamental. IN fact, there as many cases when dynamic dispatching will not be able to solve the problem on its oown: a good example is generics in Java and C# - parametric polymorphism helps enforce abstractions but isn't really object oriented.
The core concept of OOP is adding dynamic method-based dispatching to your abstract data types but I would argue that while this is useful a lot of times, its not really fundamental. IN fact, there as many cases when dynamic dispatching will not be able to solve the problem on its oown: a good example is generics in Java and C# - parametric polymorphism helps enforce abstractions but isn't really object oriented.