Code

Clean Code: 9 Rules for Newbie Programmers

Clean Code: 9 Rules for Newbie Programmers

Free Python Course: 4 Projects for Your Portfolio

Learn More
Image of George Studenko

George Studenko

The author of this material is an experienced specialist in his field, possessing deep knowledge and practical experience. He actively shares his knowledge through publications, articles, and educational materials, which makes him a valuable source of information for readers. The author's works cover a wide range of topics and areas, which allows him to attract the attention of various audiences. Thanks to his approach and professionalism, the author has established himself as a reliable and competent expert.

George Studenko is an experienced software developer specializing in artificial intelligence and computer vision. His portfolio includes projects covering a variety of technologies, including machine learning and image processing. George actively applies modern algorithms and techniques, which allows him to create effective solutions in the field of AI and data visualization.

In his career, George has successfully developed many projects, including image analysis applications and automatic object recognition systems. These developments have significantly improved the quality of visual data processing and expanded the possibilities of automation in various industries. George applies the latest technologies and methods, which makes his work relevant and in demand in the market.

This specialist’s research and publications are available on platforms such as Google Scholar and ResearchGate, which underscores his high level of expertise in the field of artificial intelligence technologies.

For up-to-date information on the latest advances in artificial intelligence and computer vision, we recommend visiting resources such as MIT Technology Review and IEEE Xplore. These platforms offer extensive analysis and research that will help you stay abreast of the latest trends and innovations in these fields.

What is Object Gymnastics?

Object Gymnastics is a concept proposed by Jeff Bay in his book “The ThoughtWorks Anthology”. It includes a set of exercises aimed at improving object-oriented programming (OOP) skills. Although this publication is not available in Russian, its ideas are actively used by programmers worldwide. Object gymnastics helps developers better understand OOP principles, improve code quality, and increase productivity. These exercises can be a useful tool for developers striving for professional growth and the creation of more effective software solutions.

Example code before and after applying object gymnastics

By applying the principles of object gymnastics, developers have the opportunity to significantly improve the quality of their code, achieving the following results: increased readability, easier support and modification, and increased resilience to errors. Object gymnastics contributes to the creation of more structured and understandable code, which facilitates teamwork and speeds up the development process. Using these principles allows developers to focus on core tasks, minimizing technical debt and increasing work efficiency. As a result, the project becomes more flexible and adaptable to changes, which is especially important in the face of rapidly changing market requirements.

  • more readable;
  • easy to debug;
  • simple to test;
  • easy to reuse;
  • simple to maintain.

Exercises in learning programming should be considered a key stage on the path to mastering skills. Although in real projects some principles can be simplified or adapted to specific needs, striving to adhere to them as much as possible is important for achieving high results. This will not only deepen your understanding of the fundamentals of programming but will also prepare you for future tasks and challenges in the development field.

Choose a simple coding task and begin working on it, following the established principles. Even if some of them may seem redundant or irrelevant, continue to apply them. This will help you improve your programming skills and increase your confidence in your abilities. Regular practice and adherence to coding principles contribute to the development of professional skills and deepening of knowledge in the field of programming.

1. Single Responsibility Principle: One Level of Nesting

The Single Responsibility Principle (SRP) states that each method in a program should perform only one specific task. This approach significantly improves the readability and structure of the code, facilitating its maintenance and testing. Adherence to the SRP contributes to a clearer and more logical software architecture, which in turn reduces the likelihood of errors and simplifies the process of making changes. Separating tasks between methods allows developers to navigate code more quickly and significantly speeds up the development process.

To achieve this goal, it's necessary to break complex code fragments, such as loops and conditionals, into separate methods. Each method should be given clear and descriptive names to ensure their functionality is easily understood. This will improve code readability and maintenance, as well as increase its efficiency and minimize the likelihood of errors. Proper code organization makes testing easier and improves collaboration with other developers.

Highly nested code is often called 'arrow code'. Image: George Studenko
  • The single responsibility principle makes code easier to maintain and modify.
  • Clear method names make program logic easier to understand.
  • Reducing the length of methods makes them easier to read.
  • Increasing code reusability.

2. Avoid using the else construct.

Nested conditions complicate the structure of code, making it harder to read and maintain. This can lead to errors and increase debugging time. Simplifying conditional logic and using alternative approaches, such as polymorphism or error handling, can significantly improve the readability and maintainability of code. This is important for improving software quality and optimizing developer workflow.

We decided to expand on the information from the original article by providing additional explanations for each method.

  • Use default values.

Let's imagine your program processes objects representing fruits, such as apples and pears. Fruit boxes can be labeled or left unlabeled. If the box is unlabeled, you assume that there are apples inside, which has become a common practice. This simplifies the processing and allows you to automatically classify the contents of the boxes based on existing rules. This approach can be useful in various applications related to fruit inventory management, logistics automation, and distribution process optimization.

In this situation, you could implement the logic as follows:

If the box is labeled, then the fruit inside is exactly the one listed on the package. This ensures that you receive fresh, high-quality products that match the description. Make sure the packaging is intact and that the information on it matches your expectations before purchasing.

Otherwise, you can assume that apples are in this place.

You can set the box contents so that it contains apples by default, and change this value only if the label "pears" is detected. This will allow you to flexibly manage the contents and adapt it depending on specific conditions.

  • Practice early return from the method.

This method is great for solving simple problems.

If condition 1 is met, action 1 should be performed, otherwise action 2 should be performed.

A variety of methods and tools can be used to achieve optimal results in different areas. These methods can include both traditional approaches and modern technologies. It is important to choose the appropriate tools depending on the tasks and goals. For example, to improve work efficiency, you can consider process automation, which allows you to reduce the time spent on routine tasks. It is also worth paying attention to analytical tools that help you make informed decisions based on data. Using the right methods and tools not only improves the quality of work but also increases competitiveness.

If the first condition is not met, perform the action and return. Handling of the second condition can be placed lower in the method, since code execution will reach this point only if the first condition is not met.

  • Move code branches to separate methods.

Methods in programming are called sequentially, and conditions are checked within these methods. If any condition is not met, the program returns to the previous level and calls the next method with a new check. This approach allows you to effectively manage the sequence of execution and handle various scenarios, which improves the structure of the code and facilitates its maintenance.

  • Use polymorphism.

Polymorphism is one of the main principles of object-oriented programming, which allows the same method to be used with different implementations within a class hierarchy. This allows for the creation of more flexible and extensible systems, since objects of different classes can be handled consistently, despite their unique properties. Polymorphism helps simplify code and improve its maintainability, making it an important tool for developers seeking to create efficient software solutions.

In this context, the possibility of replacing a complex construct with multiple conditions with a single method call is considered. This simplifies the code and makes it more readable, and reduces the likelihood of errors. Using a single method allows for efficient handling of conditions and improves the overall structure of the program. The application of this approach helps optimize code and improve its performance.

Creating a system of subclasses allows for defining a method differently, which opens up new possibilities for its application. This approach promotes a more flexible and efficient use of methods, adapting them to the specific requirements of each subclass.

  • Apply the State design pattern.

The State pattern is an effective tool when it is necessary to handle the same action, for example, moving to the next stage, differently depending on the state of the system. This approach allows for flexibility and adaptability in process management, which is especially important in complex systems. Using the State pattern, developers can clearly separate the logic for processing actions for different states, which improves code readability and maintainability. Implementing this pattern in a project helps optimize processes and increase their efficiency.

The coffee machine operates in stages: first, the user selects the desired drink, then makes a payment. After these steps are completed, the coffee brewing process begins. This algorithm ensures convenient and efficient service.

The method for transitioning to the next stage can be implemented using various checks, for example, "if I am at step N, then perform a certain action." An alternative approach is to create classes corresponding to each state and define transition actions within these classes. This approach allows you to structure the application logic, improving readability and simplifying future code maintenance.

  • Use the Strategy design pattern.

The Strategy pattern allows you to isolate a group of similar algorithms by organizing each into a separate class. This approach promotes more structured code and makes it easier to maintain, allowing you to easily add new algorithms without changing existing code. Using the Strategy pattern increases the flexibility and extensibility of software solutions.

The main class stores a reference to the current strategy to be applied. This allows you to avoid multiple "if-then" conditions, replacing them with a single call to the strategy class method. This approach improves the readability of the code and simplifies its maintenance, since changes in the strategy logic can be implemented without affecting the main class.

  • Code is not duplicated,
  • The code structure is simplified,
  • Readability is improved.

3. ​​Use wrappers for primitive types

Wrapping primitive types in classes provides data encapsulation. This makes it easy to make changes to data types during refactoring, since all edits can be made in one place. This approach significantly simplifies maintaining and updating the code, making it more flexible and convenient for development. Encapsulation of primitive types also helps improve the structure of the code and increase its readability.

This approach improves code readability. The signature of the wrapper object clearly shows what data needs to be passed to the method, which helps prevent misunderstandings and errors. This is especially important for future code maintenance and scalability, as developers can more quickly understand the data structure and functionality of methods.

The original book formulates the rule as "wrap primitive types and strings," emphasizing the versatility of this approach. Wrapping primitive types and strings allows for more flexible and safe data handling in programming. This technique helps avoid errors associated with implicit type conversions and improves code readability. By applying this rule, developers can create more robust and maintainable applications, which is an important aspect of effective programming.

If a method accepts an int parameter, this may not be informative enough. However, if the parameter is of a type such as Hour, it immediately makes it clear that an integer representing a number of hours is being passed. Validation of valid values ​​can also be implemented in the class, which will prevent invalid data, such as 36 hours, from being passed. This approach improves code readability and prevents errors related to incorrect values, which in turn improves the overall stability and reliability of the application.

  • Data encapsulation is ensured.
  • Explicit type hints appear, which improves readability.
  • Allows you to isolate similar behavior into separate methods, which increases code reuse.

4. Using a single point for calling methods

A concept based on the Law of Demeter states that you should interact only with "friends" — those classes and objects that are directly related to the current context. The basic rule is that objects should have minimal dependencies on each other, which is achieved by limiting interactions between classes through calling their methods. This approach helps reduce coupling and increase code modularity, which makes it easier to maintain and test. Using the Law of Demeter allows for more robust and flexible systems, where changes to one class minimally impact others. Using dot notation to call methods in programming languages ​​like Java and C# has its drawbacks. Calling methods in the format object.getProperty().getSubProperty().doSomething() is considered poor practice, as classes should not depend on the internal structure of other classes. This can lead to decreased code readability and increased maintainability. Instead, it is recommended to use cleaner and more understandable approaches that minimize interdependencies between classes and make code more resilient to change. Proper code organization and application of object-oriented programming principles will help avoid such problems and improve software quality.

  • The encapsulation principle is followed, which promotes better code organization.
  • The open/closed principle is followed, allowing functionality to be extended without changing existing code.

5. Don't abbreviate names for methods and variables

Abbreviated names of variables and methods can lead to misunderstandings in the code. When developers encounter incomprehensible abbreviations, their meanings may be interpreted differently. This, in turn, can cause confusion and make the code difficult to understand. To improve code readability and maintainability, it is recommended to use clear and intuitive names. Clear names of variables and methods make the code easier to work with for both the authors and other developers who may edit it in the future. Proper naming practices contribute to clearer and more maintainable code.

If you need to shorten a method name, consider whether it's doing too much. This may indicate a violation of the Single Responsibility Principle (SRP), a key principle in software development. Adhering to the SRP helps create cleaner and more maintainable code, improving readability and simplifying testing. When designing methods, it's important that each method performs only one task, which promotes more efficient code management and reduces the likelihood of errors.

Now that you understand this, it's crucial to avoid abbreviations in naming. Clear and understandable names promote better understanding and make code easier to maintain. Using full words and phrases improves readability and makes the code more accessible to other developers. This also helps avoid misunderstandings and reduces the likelihood of errors when working on a project. Following this principle will significantly improve the quality of your code and make it easier to modify in the future.

  • Adherence to the Single Responsibility Principle (SRP).
  • Minimization of confusion and misunderstanding within the development team.
  • Reduction in code duplication and simplification of its maintenance.

6. Keep classes compact.

Limiting the size of classes is an important aspect for increasing their efficiency. The smaller the class, the easier and faster it is to use. This allows you to simplify the code, improve its readability and increase reusability. Small classes are easier to test and maintain, which ultimately leads to higher quality software. Optimizing classes also helps reduce the load on the system and speeds up program execution, which is especially important in conditions of high performance requirements. By implementing the principle of limiting the size of classes, you contribute to the creation of more structured and efficient code.

  • Methods no more than 15-20 lines,
  • Classes up to 50 lines,
  • No more than 10 classes in one package.

Ultimately, if your class is aimed at solving a specific problem, its size should be minimal. This will improve the readability and maintainability of the code, and also simplify its testing. Optimizing the class size contributes to better dependency management and facilitates further modification. Focusing on a single task helps avoid redundancy and makes the code more efficient.

  • The Single Responsibility Principle (SRP) is supported,
  • Module complexity is reduced,
  • The code becomes more consistent and understandable.

7. Limiting the Number of Instance Variables in a Class

The principle that a class should contain no more than two instance variables helps maintain a clean software architecture. This approach implies that each class should be responsible for one specific state, which prevents violations of the single responsibility principle. Adherence to this principle improves the readability and testability of code, simplifies its maintenance, and increases the flexibility of the system as a whole. Optimizing classes with a minimum number of instance variables allows developers to quickly identify and fix bugs and promotes more efficient teamwork.

An instance variable, also known as an attribute, is an element that stores the properties of a specific object. It differs from static variables, which apply to the entire class, as well as from local variables created within class methods. Understanding the differences between these variable types is key to effective programming and managing object state in object-oriented programming.

Complying with this requirement can seem challenging, especially for developers who often encounter classes with many parameters in constructors. However, this approach is not optimal. An excessive number of parameters can reduce the readability of the code and make it more difficult to maintain. It is recommended to use more effective design practices, such as the Builder pattern or the use of parameter objects, which will help make the code more understandable and convenient for further use.

It is recommended to consider combining related parameters into a single object. This will not only simplify the class structure, but also encourage you to rethink its architecture and functionality. This approach will improve the readability of the code and simplify its maintenance, which will ultimately lead to more efficient development and project management.

  • Ensuring high cohesion of modules.
  • Adhering to the principles of encapsulation.
  • Reducing the number of dependencies between different program components.

8. Effective Collections: Creating First-Class Classes

This principle is similar to rule #3, but focuses on managing data collections.

You can keep your collection simple within the class, but wrapping it in a separate class allows for easy refactoring in the future. This allows, for example, changing the collection type without having to change the external interface. This approach increases code flexibility and simplifies maintenance, which is especially important as the project evolves.

The authors of The ThoughtWorks Anthology emphasize that it's best to avoid unnecessary components in a wrapper class and focus solely on the collection itself. However, it's acceptable to include methods that provide manipulation of collection elements, such as filtering, as well as add and remove operations. This approach leads to cleaner and more understandable code, allowing developers to effectively interact with the data within a collection.

  • More functional wrapper classes are created for working with simple collections.
  • Responsibility for the collection's behavior is concentrated in one place, making it easier to make changes.
  • The principle of encapsulation is followed, which improves data security.

9. Eliminate the use of getters and setters

Don't take responsibility for tasks that should be performed within the class. Let the class handle its functions independently. Follow the principle of "Pass information, don't ask questions." This not only simplifies the architecture of your code, but also increases the level of encapsulation, which leads to a cleaner and more maintainable structure. This approach improves the interaction between components and makes the system more resilient to change.

  • Adherence to the open/closed principle allows for extension of functionality without changing existing code.
  • Code maintenance and testing are simplified, as classes become more independent.
  • Reduced coupling between system components, which increases its resilience to change.

Next Step: How to Move Forward?

Now that you have the basic knowledge, it is important to keep moving forward. At the initial stage, following new principles may seem difficult. You may be tempted to revert to your usual way of doing things. Instead, view this as an opportunity to test your abilities. Pull yourself together and practice actively, without allowing yourself to indulge. Consistent practice and persistence will help you strengthen your skills and achieve success.

It's important to remember that in day-to-day development, you don't always need to strictly adhere to all the rules. In practice, situations often arise where compromises become more appropriate and effective. Choose the methods and approaches that best suit your needs and those of your team. This flexible approach allows you to adapt to changing conditions and improves overall productivity.

Python Developer: 3 Projects for a Successful Career

Want to become a Python developer? Find out how easy it is to master the profession and get expert support! Read the article.

Find out more