What Makes Good Code? (2/7)

João Bertolino
The Startup
Published in
6 min readSep 3, 2020

Language is important

This is the second section of the article about how to make good code. If you miss the first part, click here. I would like to make this article as accessible as possible, so if you are already a programmer, you can skip this part, but it is always good to remind of the basics.

Every code is expressed in a language that communicates with the machine. Computers only understand basic instructions, assembly language, which is the closest thing to the zeros and ones of the logic circuit. The complexity of writing code at this level grows quickly, because you have to manage loading from memory to registers (place where processor can perform operations); saving data from register to memory; process basic operation like adding, copying with registers; and jump directly to some instruction or based on the value of a register. This is a grotesque simplification, but I think it gets the point of how low level these instructions are. When you are at this level you can do anything and it is not because you can do something, that you should be doing it.

When humans must handle every tiny detail of code flow and memory, a lot of common mistakes happen and there is nothing we can do about it. So, it was a necessity to have languages that we could communicate with machines at a higher level and leave that tedious task to a compiler to figure out. There are thousands of high-level computer languages, but there are few programming paradigms. Understanding programming paradigms is the key to good code. We are going to take an overview on the most influential paradigms for general purpose programming.

The procedural programming is the closest to assembly code, it adds two basic concepts: procedures (or functions) and variables. Procedures/Functions are black boxes with a sequence of instructions that receives parameters and have a return value. Variables are an abstraction of memory, so instead of dealing with just numbers in binary format we can have many types of numbers, letters and custom types that are more useful to programming. Here, functions are first-class citizens so you can call functions inside functions and use functions as parameters. If you want to calculate the magnitude of a 2D vector in a procedural language, you will have something like:

float getMagnitude(float x, float y)

The line above is in C-like syntax and it expresses the declaration of a function that receives two numbers (float x and y) and returns a number (float). By the way, C is a popular language created in the early 1970’s and still influences a lot of programming languages today. Float is a variable type that represents real numbers. If you want to get the magnitude of a 2D vector you do not have to know how it works, just use this function passing the values of the X and Y components of the vector.

Black box is an important concept for software engineering. The function above is a good example of black box because it is stateless. It doesn’t matter in which order you call this function or which combination of parameter you use, it always returns the right value. This is true because it is a mathematical function, but most of the time you need to add state to your software and it’s the root of many problems. The worst place to add state in your software is inside a black box. Good code uses stateless black boxes and it is concerned about the current state of its systems.

Object Oriented programming is an evolution of the procedural. It adds the concept of Classes. In this, your functions and variables must belong to a Class that consist of a group that operate on the same data. A Class has the control of what can be accessed by other Classes, so the programmer does not mess with variables (or state) that they should not touch. This is a huge problem in procedural programming, because all data can be accessed. When you create a Class, you are instantiating an Object, that is why it is called Object Oriented. A Class is the model or type of an Object. Classes can inherit from other Classes. There is also the concept of Interfaces, that are kind of contracts that you can add to your Classes that guarantees that those Classes have specific functions (now called methods).

In a whole different approach, we have Functional programming. It is not aiming to evolve from the imperative model to tell the computer what to do. It emerges from the concept of making function like in mathematics, where you do not have a sequence of instructions, but you declare a function that evaluates and returns other functions. It is a mixture of lambda calculus, lazy evaluation, immutable data and some other ideas that is kind of tricky to explain better in a few words, so I won’t even try. Historically, functional programming was much less popular than the imperative ones, but most modern languages are adding Functional elements to it. It might not be handy to write all your software in pure Functional programming, but in some cases it thrives.

In the paradigms we discussed so far, we saw that all the evolution was to control access to memory and avoid human error. Even with all the validations that Object Oriented has added to help programmers, the most common problem of software is getting into an invalid state when two or more Classes use the same resource. This problems gets more evident when we are making parallel software like using multi-thread (where you create simultaneous process that might run in different processor or threads at the same time) or distributed system (system that uses a cluster of computers to run), because if more than one process is acting in the same memory at the same time we have to deal with this conflict. This is a big problem in Computer Science and Functional programming uses the concept of immutable data. Immutable data solves access issues by avoiding reuse of the same memory location. Each function receives its parameters and returns a result that is a new entity in a new memory address, that way it will never generate conflict.

High level languages can be classified as compiled or interpreted. Compiled languages use a compiler that is a software that translates the code to a specific processor architecture. Once the code is compiled, the generated application can only run on that specific type of processor. Interpreted languages run over another process called virtual machine, so the software does not have to be compiled in every architecture out there, you just need a virtual machine running. Compiled languages are by their nature faster than interpreted languages, but interpreted languages have more functionalities and they are easy to deploy to different targets. It is common in computing where we have such opposition to have a hybrid approach. They are the precompiled interpreted languages, which are popular today.

The last topic about languages is strongly and weakly typed. The strongly typed languages are languages where variables assume a type and it is fixed, if you want to change the type of this variable you have to create another variable and perform a conversion. In weakly typed language, this is not necessary. The variable will be evaluated with the type that the context asks for. This can be handy in some cases and many interpreted languages are weakly typed, but it can lead to a lot of mistakes and unexpected behavior if you do not master the language. Use tools to validate your code, especially when using interpreted weakly typed languages.

In the real world we use languages all the time. You can only comprehend this article because it is written in a language that you understand. Lera Boroditsky studies how language influences us. She claims that language shapes the most fundamental human experience like space, time, causality and relationship. We have a lot of other languages in your life, for example, math, traffic signs, music, body language, etc. This is no different in computer languages. The language you use shapes the way you solve problems and write quality code.

That is for today. Now that we know the basics of computer languages, in the next section we are going to talk about the SOLID principles.

--

--