I don't fully comprehend this concept: having a function perform only one operation and a class performing just one task. How do we determine what qualifies as a single task?
To illustrate, let's consider a class that receives a POST request, does error processing and validation. While these could be considered different tasks, it might seem odd to divide them into subclasses. In such a scenario, it becomes effortless to receive and process the request aimlessly. Ideally, the request should be sent to its intended destination - like mail, DB, or file. Should we create a separate class for each, or is it better to use a method within its respective scope instead of globally declaring access from outside?
The same ideology applies to functions as well.
The concept of code reuse is highly essential in software development. If a class solves just one specific task, it becomes easier to reuse within or even outside the project. This approach leads to a highly abstracted class that remains independent of the project.
This principle aligns with the ancient "divide and conquer" philosophy, which promotes breaking down big problems into small, solvable ones. In doing so, we can focus on solving one particular task at a time, leading to better efficiency.
Moreover, it's best to avoid using global variables or external access wherever possible. Instead, it's advisable to pass all necessary data via arguments. This ensures better encapsulation and organized code.
A function may perform two actions, but to achieve this, it should call two subfunctions internally. Each of these subfunctions should perform a smaller, specific action. This approach helps create a higher level of abstraction in the overall function.
Mixing high-level abstractions with low-level details is generally frowned upon since it can make code reading difficult. It forces programmers to switch back and forth constantly between the technical implementation and the business logic of the subject area, which can be mentally taxing.
In other terms, maintaining a clear separation of concerns at different abstract levels is essential for creating clean, readable, and reusable code. Programmers must strive to maintain a balance between modularity and complexity in their codebase to foster better collaboration, reduce errors, and ensure scalability in the long run.
When it comes to determining what qualifies as a single task, it involves breaking down the responsibilities of a class or a function into cohesive, logical units. This granularity allows for better organization of code and facilitates easier testing, debugging, and reuse.
In the context of a class that receives a POST request, performs error processing, and validation, the idea is to assess whether these tasks can be separated into distinct responsibilities. For instance, receiving the request, error processing, and validation might each be considered separate tasks, and each could potentially be encapsulated within its own class or function. By doing so, each class or function would be responsible for only one specific aspect of the overall operation.
However, I understand your concern about potentially dividing these tasks into overly fragmented subclasses, which can complicate the design and introduce unnecessary complexity. In some cases, it might be more practical and pragmatic to have a single class responsible for handling all related tasks, as long as it doesn't violate the single responsibility principle or lead to a monolithic and unwieldy class.
When considering whether to create separate classes or use methods within the same class for different tasks, the decision should be guided by the broader architecture and design principles of the system. For example, if the behavior of sending the request to its intended destination (such as mail, database, or file) varies significantly between each destination, it might be beneficial to create separate classes to encapsulate this behavior. This would enable better encapsulation, easier maintenance, and support for potential future extensions. On the other hand, if the behavior of sending the request is largely similar across different destinations, employing methods within the same class could simplify the design and reduce unnecessary complexity.
In essence, the decision-making process for structuring classes and functions should prioritize modularity, maintainability, and adherence to the single responsibility principle. It's also important to consider the specific requirements, use cases, and constraints of the system at hand. By carefully balancing these factors, you can create a well-organized, flexible, and robust codebase that can evolve and adapt to changing needs over time.