Green degree

Principles

Open Close Principle

The Open Close Principle states, that a class must be open to extensions, but closed to modifications.

Technically it means that it should be easy to extend the behavior of a class but difficult to change existing behaviors of that class.

What does this mean practically?

Think about that you have two types of customers. The first type of customer uses the metric system in his orders, the second type of customer uses the imperial system instead. Your pricing scheme is based on the metric system. So whenever a customer places an order, you have to check which system the customer uses. Lets assume to use meter and foot for lengths. In the early days of your project, someone added a boolean flag to the customer database to indicate that a customer uses the imperial system - false = metric, true = imperial. Everywhere in the code they added if-statements to check for converting length values from foot into meter.

You got the request to add support for orders with lengths in cm and inch. So a boolean flag is no longer enough.

This is the first time you hit the Open Close Principle.

To perform the requested change you have to do a lot of work. You have to change the boolean flag to something else, i.e. an enum or string to specify which unit the customer wants to use. You also have to find all usages of the old flag and change the logic that it considers two more units, i.e. by replacing the if-statement with a switch-statement.

So effectively, the existing code is hard to extend, it requires a lot of modifications and it includes the risk of adding a bug.

What could have been done better from the beginning?

Instead of handling length values as pure numeric values, they could have made a class for UOMs, unit of measurements. A UOM has a quantity, the numeric value, and a unit. That UOM class could have a GetValueInMeter() method. So the only place to do conversions would be in that class. That UOM class could be used in your pricing scheme. This would eliminate the repetitions of the conversion code. It would make your current request - to add support for cm and inch - much easier.

But could this be simplified even more, maybe without hard-coding the units? Yes, you could implement a UOM-converter with a Convert(sourceUom, targetUnit) method which returns the converted value. The conversion logic would be no longer in the UOM itself. That UOM-converter could also read the supported units and conversion details from a configuration file and your pricing scheme could be either always use meter as target unit or read the target unit from a configuration file too.

Tell, don't ask

Some people say, that classes should have no property getters. The reason is, to tell a class what to do, and not to ask for its state to decide from an outside perspective what to do.

I would agree to that, but it requires another rule:

Separate logic from state

State is quite simple: all models such as Data-Transfer-Objects or entities in Domain-Driven-Design. The objects should have only state - only properties - but no logic.

Logic is exactly the opposite: all classes without (visible) state. Those components manipulate the models.

So you tell the component what to do with the model you provided.

This rule has a quite wide gray-zone. Is a collection state or logic? A collection has state: the number of items and the items themself. But it also has logic, such as Add(item), Remove(item), Contains(item).

Algorithms + Data Structures = Programs

Niklaus Wirth

Law of Demeter

The Law of Demeter is about limiting the interaction between objects. It could be simplified to Don't talk to strangers.

This is relevant because accessing objects across several links is a service chain and leads to close coupling.

The Law of Demeter allows accessing the following methods:

  • Methods of the class itself
  • Methods of parameters
  • Methods of associated classes
  • Methods of self-created objects

A lot of modern frameworks and design pattern violate this rule:

  • Fluent APIs
  • Lambda expressions
  • Object-Related-Mappers such as Entity Frameworks navigation properties
  • Hierarchical Data-Transfer-Objects

Strict handling of the Law of Demeter would not allow statements like this:

int margin = config.Pages.Margins.Left;

The Law of Demeter would allow access to config.Pages only and not further to .Margins.Left.

Practices

Continuous Integration

The integration of an application is often postponed and done manually. Automating this is then done at a later stage of the project.

This is often a missed chance of improvement. Automate the integration of your application together with the Continuous Deployment (we will talk about that later) allows you to identify deployment issues early.

Your application should be executable at any time and be tested with other integrations automatically.

One reason for doing continuously test the integration is to ensure that the application runs on the platform the customer specified. You could have your application running fine on your developer machine and the test run fine on your build agents, but this does not mean, that it will run on the machines of the customer.

Statical Code Analysis

Statical Code Analysis has become the definite standard in all modern programming languages/platforms. Especially in the AI era it is mandatory to have these kind of metrics to ensure that the code quality fulfills the requirements.

There are several analyzers available with a wide variety of rules to configure and check.

Inversion of Control

We talked about Dependency Inversion Principle earlier. One issue there is, that you have to provide the dependencies manually, which requires extra code. This is where Inversion of Control comes into play.

Instead of manually constructing instances of your classes at all, you use an IoC container to do that for you. Whenever you need an instance of a class, you request it from that container.

Following the Interface Segragation Principle and Favor Orchestration over Inheritance, this becomes a very powerful pattern.

At the start of your application you register your services to the IoC container by specifying which interface shall be resolved into which class. Usually those registries support three kind of scopes: singleton, scoped and transient. During runtime you can then get from IoC instances of your requested interfaces. Singleton scopes will return you the same instance on each request. Scoped behaves similar, but returns an individual instance for each created scope. Transient will return a new instance for each request.

Share Experience

As mentioned earlier, you should read books about software development and read code to get familiar with various code styles and techniques. You should also participate professional events. But that's all receiving information.

Share your experience with others is important too.

Discuss your code with your colleagues apart from pull-requests and code reviews. Get feedback whether people think that your code is easy to read and easy to maintain and in which way you could improve your way of working and your work results. Find people you can discuss developers topics in general.

You could organize an internal developer conference in your division/company.

You could become a speaker at a developer conference.

Share your experience. Become a mentor for others.

Error Measurement

Errors happen and mistakes during software development cause bugs. No one is perfect and usually errors and bugs are caused by unclear or missing specifications in the requirements/features. In any case, errors should be tracked and recorded to not be forgotten.

It was never that easy as today to collect telemetry data of your application than today, thanks to OpenTelemetry. This is a standard for telemetry data and supports logging data, traces and metrics. Most modern programming languages/platforms supports that standard out of the box and there are several products on the market to collect, track and visualize the data.