Intro
One of The biggest problems in the development and maintenance of large-scale software systems is complexity — large systems are hard to understand.
In any non-trivial system, there is complexity inherent in the problem that needs to be solved.
Also, we have other contributors to the complexity of the code that does not come from the essence of the problem but from the technical decisions and individual developer experience.
Those are
• handling of state
• code volume
• flow of control through the system (about the order in which things happen)
Other causes of complexity
• duplicate code
• dead code
• abstractions (unnecessary or missed)
• poor modularity
• poor documentation
One of the first decision about solving problem is decision about programming language (programming paradigm) decision between object-oriented programming or functional programming. Each approach suffer from different problems when applied to a large-scale system. The goal of this article is to point out that it is possible to take useful ideas from both - combined approach has potential for simplifying the construction of large-scale software
Mechanisms that are commonly used to try to understand system
• testing (to understand a system from the outside — as “black box”)
• informational reasoning (to understand the system by examining it from the inside – reading code)
• formal reasoning (documentation, requirements)
All ways of attempting to understand a system have their limitations. This includes both informal reasoning (reading code) — which is limited in scope, imprecise and hence prone to human error — as well as formal reasoning — which is dependent upon the accuracy of a specification.
Object-oriented approach to managing complexity
In most forms of object-oriented programming (OOP) an object is seen as consisting of some state together with a set of methods for accessing and manipulating that state
One organizes the program around objects: a set of attributes, plus methods that use/change these attributes. A class is a "blueprint" to create objects, all of the same structure. Classes usually model something: a car, an employee, an address... And objects are also values, to be passed along over functions.
OOP can be overused tho. A custom object, or a class should describe 1 specific thing and do it well. Also a custom object should be modifiable only by it's methods, so there's 1 clear controlled way (by checking if the applied modification is valid etc.. depends on the needs).
Functional Programming approach to managing complexity
Functional programming is not only about creating and calling functions - any style of programming has them - but about functions being first-class citizens. In FP, functions can be assigned to variables, passed along to other functions, returned from functions, as it they were any other value, like numbers.
The primary strength of functional programming is avoiding state (and side effects) - which implies that when supplied with a given set of arguments a function will always return exactly the same result (speaking loosely we could say that it will always behave in the same way).
Everything which can possibly affect the result in any way is always immediately visible in the actual parameters.
The main weakness of functional programming is the flip side of its main strength — namely that problems arise when (as is often the case) the system to be built must maintain state of some kind
Diff
And that's basically what makes functions and classes different. Functions is a block of code that does a specific thing and can be used anywhere. A custom object is a set of small things that describe a bigger image and should be controllable.
Real-World Application: Combining OOP and Functional Programming
Object-Oriented Programming is excellent for modeling complex entities with rich interactions. It allows developers to encapsulate state and behavior within objects, making the system easier to manage by segmenting the codebase into discrete, self-contained units. On the other hand, Functional Programming shines in managing logic in a predictable, side-effect-free manner, which simplifies reasoning about the code.
Use OOP to structure your code, model your entities, and manage interactions. Then, leverage FP to handle state transformations and business logic, ensuring your code remains pure and predictable.
Example in Java
Let's consider a simple example in Java to illustrate this approach. Suppose we're building a system to manage user accounts.
In an OOP style, you might define a User class to encapsulate the state and behavior of a user:
public class User {
private String name;
private int age;
private String email;
public User(String name, int age, String email) {
this.name = name;
this.age = age;
this.email = email;
}
public String getName() {
return name;
}
public int getAge() {
return age;
}
public String getEmail() {
return email;
}
public void updateEmail(String newEmail) {
this.email = newEmail;
}
// Additional methods for user behavior...
}
However, when it comes to handling state logic, such as validating the new email or transforming user data, you can adopt a functional approach. Here, you might create a pure function to validate an email:
public static User updateUserEmail(User user, String newEmail) {
if (isValidEmail(newEmail)) {
return new User(user.getName(), user.getAge(), newEmail);
} else {
throw new IllegalArgumentException("Invalid email address");
}
}
This combination not only simplifies the construction of large-scale software but also makes it more resilient to change, easier to maintain, and more aligned with the goals of both programmers and architects
Top comments (0)