Sunday, March 5, 2023

When is too less code too much?

Let's start with one very nice quotation from 'Java Performance: The Definitive Guide: Getting the Most Out of Your Code' book (available here):

... but the conflict here is that a small well-written program will run faster than a large well-written program. This is true in general of all computer programs, and it applies specifically to Java programs. The more code that has to be compiled, the longer it will take until that code runs quickly. The more objects that have to be allocated and discarded, the more work the garbage collector has to do. The more objects that are allocated and retained, the longer a GC cycle will take. The more classes that have to be loaded from disk into JVM, the longer it will take for a program to start. The more code that is executed, the less likely that it will fit in the hardware caches on the machine. And the more code that has to be executed, the longer it will take.

This summarizes a lot of stuff and I think that does not need any further explanation regarding mentioned cases.

Generally, in programming, there are principles like KISS (Keep It Simple and Stupid), DRY (Don't Repeat Yourself), YAGNI (You Aren’t Gonna Need It), YDNIY (You Don’t Need It Yet) trying to teach us to write as less code as possible. 

You could potentially say that the code is our enemy - quoting:

Code is bad. It rots. It requires periodic maintenance. It has bugs that need to be found. New features mean old code has to be adapted.

The more code you have, the more places there are for bugs to hide. The longer checkouts or compiles take. The longer it takes a new employee to make sense of your system. If you have to refactor there's more stuff to move around.

... and more.

And one quote, which we can use here also is (unfortunately, I don't have a source or author):

A perfect system is one that does not exist but still fulfills its function.


Benefits of writing less code

Less code is easier to understand, and it decreases cognitive load - the mental effort required to understand the code. When there is less code, it is easier to see the overall structure and flow of the program, and it is easier to understand how each part of the code is related to the rest. This makes it easier for others (and yourself) to work with and modify the code.

Less code is often more efficient. By writing less code, you can avoid unnecessary computations and reduce the number of function calls.

Less code is easier to maintain and debug. This makes it easier to find and fix problems when they do arise. For each new line of code, we need to verify it works properly. So, the lines that don't exist don't need tests, and they are not bringing new bugs. 

But you should not understand it in a way that you should write as less code as possible at all costs.


When to write more code

If you blindly apply the principles like YAGNI, you can hurt yourself later on. There are, of course, reasons why we can "waste" more lines of code. For example for better abstraction, more interfaces, even if it might look like they are not needed right now. Things decreasing coupling. Things that are self-explainable and improve better code organization. 

It is good to think in terms of smaller chunks of code, splitting the application (doesn't matter if we are going to call them functions or methods). This allows us to organize the code in a better way.

Smaller pieces of code are having simpler logic, helping us with easier code analysis (that is provided by current IDEs) - for example easier code duplication detection. We don't need to do the same change in multiple places, which brings fewer bugs. It allows us to use more abstract, reusable patterns.

Shorter pieces of code help us, for example, to avoid conflicts during merging, that can be caused by parallel work on a complex method changed by multiple developers at the same time.

Code splitting helps us with a clearer separation of concerns and Single Responsibility Principle (SRP) application. 

Shorter pieces of code are typically easier to test, as they have fewer code paths and are less complex.

Shorter functions/methods usually have a lower number of parameters, making testing easier. However, if a function/method has too many parameters, it may be a sign that the method is trying to do too much and should be broken up into smaller methods.


Code splitting

Code splitting can follow the rule of ten, which suggests that methods or functions should ideally have no more than ten lines of code. Additionally, one class should not have more than ten functions/methods, and one package should have no more than ten classes. While this is not a hard and fast rule, it promotes more readable and maintainable code as a general guideline.

Another approach is to consider the size of the IDE window - more exactly, the part where you are editing your code. A function/method should fit within the visible part of the window, allowing you to see its entire definition without scrolling.


Grouping chunks of code

Grouping chunks of code into sets that are more focused on one specific concern can help fulfill the Single Responsibility Principle (SRP).  

Proper naming conventions, such as using names containing well-known design patterns (to don't have the codebase just full of various "managers"), can make it easier to understand what the code is doing without the need for in-depth investigation.

These sets (files, classes) of functions/methods can contain more concrete or more abstract functionality and structures. These groups e.g. of classes can be grouped into modules. Separating different levels of abstraction into separate, independently deployable modules can help us to build a more maintainable application, where changes are isolated to specific parts or modules, making testing easier and more focused. We don't need to be so afraid that we broke something in a different part of the application. This makes testing easier and more focused since you don't need to test completely everything.

In object-oriented programming (OOP), more stable and abstract modules can use inheritance - since they should cover code business concepts, that are not going to be changed so often.

While more volatile, concrete modules should rather use object composition to allow for more flexibility in adjusting functionality to business needs.

Generally said - write as less code as possible, but at the same time, maximize positive impact on business in the long-term by prioritizing good architecture to facilitate easier maintenance and extensions.

No comments:

Post a Comment