Objects and classes are not terrible, but they are not essential to good programming, and there are better ways to do things. Many programmers (most, that I've met) think OOP is not only the best way, but the only sensible way to do things. Here are some arguments to the contrary, and in favor of a functional or function-oriented approach.
Hierarchies have an appealing simplicity, but class hierarchies don't make sense, because functionality is not hierarchical.
Technically, object-orientation can occur in functional languages (OCaml is built around it). But, typically, OOP is a tool for imperative programming. Though it's not always obvious, many of the features of OOP are efforts to control side effects. This is necessary, because side effects suck. Pure functions and immutable data take care of this problem without the complex machinery of OOP. In fact, when you switch from programming with side effects to using pure functions, many of the concerns addressed by OOP become irrelevant. Implementation hiding is trivially accomplished by functions; modularity is not only possible with pure functions, it's easier.
This is not to say that you can't use classes and objects in a functional context, just that they are not necessary for some of their typical purposes.
My friend told me he liked OOP, because he wrote all his objects like "self-contained state machines." Except that is impossible; if they were self-contained, they would be separate programs. At some point, you have to build one big state machine out of all the little ones; that is, you have to compose them. This is where all the trouble comes in. This is where you get tight coupling, or lasagne-code, or any of countless anti-patterns of OOP. State machines are hard to compose.
Functions, on the other hand, are trivially composable. Take the output of one, plug it into the input of another. Voila, you have one big function! No muss, no fuss. The downside is, it's usually easier to express a small process as a state machine than as a pure function, but that's where local side effects come in.
Classes generally keep their state private, and offer a public interface for manipulating that state. This is supposed to allow the programmer to ensure that an object's state is consistent. But how does one ensure that the state of the application is consistent? Just like plain old procedural programming, an OO design offers no comprehensive record of the state of the application. Instead, statefulness is scattered throughout the application like dust. Ironically, hiding the state of each component from other components can make large-scale side-effects more difficult to manage. Countless pieces of inaccessible data reach across the application, affecting potentially any other part of it, and there isn't any easy way to test that the application is even in a valid state.
This is just a special case of "Side effects suck." The function-oriented way is to disallow state as such, instead passing all information needed for a function as an input to it, and allowing only the return value as output. Stateful behavior can be simulated, in a cleaner, more controlled, more testable manner by using the State monad; see the article on customized function chaining for an explanation.
Object-orientation is based around creating the illusion that data has agency. But it doesn't - data doesn't ever do anything. The computer might do something, a function does something when it is called, but data is just there. Which leads me to...
Suppose we have a user object and a database object, and we want
to log the user in. Should the database log the user in, or
should the user log in to the database? In other words, which
class should have the
login method? (The fact that the OO
methodology requires us to answer such inane questions is reason
enough to hold it suspect). The correct answer is: neither. The
login is an interaction between the two. There is no logical
reason to put that interaction inside one class or the other; an
interaction is best represented by a function. In fact, that's
basically what functions are.
Most often, various aspects of OOP are used to accomplish goals for which they were not designed and are not necessary. This would seem to be a complaint about the misuse of OOP rather than OOP itself, but the phenomenon is so ubiquitous that I feel justified addressing it here.
The only feature that really sets OO apart from other mature programming stylse is inheritance. The classic rule for inhertiance is the "substitution principle", explained here by Robert C Martin:
Functions that use pointers or references to base classes must be able to use objects of derived classes without knowing it.
The problem is, I have almost never seen a real-world example of this scenario. You rarely encounter two different, useful data types for which the entire functionality of one is contained in the other. The times that I thought I had such a case, I usually discovered later that I had been mistaken; exceptions and workarounds started accumulating, and I eventually had to refactor. Most times, when I've seen inheritance done "right" - or, at least, without being a mess - it has usually been an instance of multiple classes inheriting from an abstract base class. But this is just an emulation of "modules" or "mixins"; the same relationship would be better expressed with type classes