4R Digital Help

Clean Code

Meaningful Naming

It is better to have a long name than to need comments to explain. This includes lambdas as well. Using appropriate names throughout the code will ensure easy understanding of the context and facilitate maintenance.

Bad:

var result = this.service.Get(x => x.Name, "Adams");

Ideal:

var customers = this.customerService.GetByLastName(customer => customer.Name, "Adams");

In the example above, variables, methods, lambda parameters, and properties all have appropriate and self-explanatory names.

Lean and Cohesive Methods

Keep your methods (or basically any other scope) simple, direct, and focused on a single main objective.

Limit of 3 Maximum Parameters per Method

Ideally, you should prefer only 2, unless justified. More than that is a strong indication of a bad design, so it's preferable to use a DTO with all the necessary properties instead of parameters.

In practice, parameters represent the dependencies of a method, and this can be a strong indicator that it violates the SRP. It's important to always keep methods lean with a simple purpose, making the code readable and maintainable.

Encapsulate Complex Conditionals

Any conditional composed of more than one validation becomes complex when done in the same line. Encapsulate these conditionals in specific methods and, even better, separate them by context.

OK:

if (value != null) { /// Code }

Bad:

if (value != null && value + 1 < 5) { /// Code }

Ideal:

var isValid = IsValid(value); var isLessThanFive = LessThanFive(value); if (isValid && lessThanFive) { /// Code } private bool IsValid(int? value) => value != null; private bool LessThanFive(int value) => value + 1 < 5;

Use Standardized and Consistent Formatting

All code must follow a consistent pattern of formatting, indentation, spacing, and capitalization.

Be Careful with the Use of Flags (Boolean Parameters)

Boolean flags can be passed to methods via parameters, as long as they are not used to decide flows. This behavior usually indicates a violation of the SRP, where the method has different modes or operation flows.

Similarly, the use of enums can also indicate a violation of the SRP when used in the same way.

Side Effects

It is also common to violate the SRP when there are side effects, i.e., hidden and additional operations in addition to the main function of the method. A good example is a method that returns the result of a calculation and makes changes to the state of the class, such as incrementing a value. Ideally, the method should have only one function, and state changes should be handled separately.

Avoid Explanatory Code Comments

In very few cases, comments are useful. However, in most, they can be avoided through code encapsulation. The reason is simple: comments are less visible and are often ignored, becoming quickly outdated.

Allow the Code to Breathe

Lines of code all together create a large block of code that is difficult to read. Group related lines of code only, skip a line, and allow the code to breathe.

Bad:

public Customer GetCustomer(string id) { if (string.IsNullOrWhiteSpace(id)) throw new ArgumentNullException(); var uri = $"http://test.com/api/customers/{id}"; var httpClient = HttpClientFactory.Create(); var response = httpClient.Get(uri); response.EnsureSuccessfulRequest(); return JsonSerializer.Deserialize<Customer>(response.Content); }

Ideal:

public Customer GetCustomer(string id) { // Guard if (string.IsNullOrWhiteSpace(id)) { throw new ArgumentNullException(); } // Setup var uri = $"http://test.com/api/customers/{id}"; var httpClient = HttpClientFactory.Create(); // Execution var response = httpClient.Get(uri); response.EnsureSuccessfulRequest(); // Return return JsonSerializer.Deserialize<Customer>(response.Content); }

Note: Comments are only here to facilitate the understanding of the example. They should not exist in real code and already indicate that the code can be refactored.

Never, EVER, Use Regions

Just because a feature exists doesn't mean it needs to be used. Regions make the code very difficult to read because they hide parts of it and contribute immensely to the increase in scopes, ALWAYS causing serious violations of the SRP with gigantic methods and classes.

Here are common false justifications for regions and the best alternatives:

  1. Grouping code with title
    When this happens, it's clear that the class or method is too large and needs to be grouped. It's obvious that it violates the SRP, and the class or method needs to be refactored.

  2. Code organization
    Probably similar to the previous one, but only to organize the code. The way a class should be organized follows the general scheme:

  1. Concealment


    No code should be hidden for any reason!

Order of Methods

Methods should be ordered by use, followed by their dependencies in the same way.

public Customer GetCustomer(string id) { if (string.IsNullOrWhiteSpace(id)) { throw new ArgumentNullException(); } var httpClient = this.GetHttpClient(); var response = this.GetValidResponse(uri, httpClient); return this.Deserialize(response.Content); } private HttpClient GetHttpClient() { var uri = $"http://test.com/api/customers/{id}"; return HttpClientFactory.Create(); } private HttpResponse GetValidResponse(string uri, HttpClient httpClient) { var response = httpClient.Get(uri); response.EnsureSuccessfulRequest(); return response; } private Customer Deserialize(string content) => JsonSerializer.Deserialize<Customer>(content); public void NextUnrelatedMethod() { /// Code }

Automated Tests

Software tests are the only guarantee we can give that this software operates correctly, safely, and reliably. It also ensures that it will not fail to do so during changes, improvements, and corrections.

That said, software should ALWAYS be tested, and their automation is the best way. Manual tests are very limited. Defining the best testing strategy that works for the developing software will allow it to be testable, becoming part of the engineering process. As a general rule, unit tests are the minimum that should always be present.

There are other types of tests, such as Integration, Acceptance, Manual, Performance, Load, etc.

Unit Tests

This is the most basic test that will validate each code unit (a method can contain one or more units

) against possible scenarios to ensure correct operation. In this type of test, all external dependencies of the unit, also known as the "Subject Under Test or SUT," are mocked because they are unnecessary.

Integration Tests

These tests validate interfaces or boundaries and ensure that the integration between parts works as expected. Good examples are:

  • REST API endpoint tests, where we need to ensure that invalid parameters or data will return a Bad Request;

  • If an entity was not found by the id, it should return Not Found.

Integration tests typically have mocked dependencies as well.

Acceptance Tests

These tests should evaluate an entire feature against acceptance criteria, ensuring that the business requirements of the story or task were correctly implemented. They typically use real (or approximate) data without the need to mock dependencies.

Mandatory Reference

Last modified: 03 June 2024