The first rule for good software design: Get the names right!
Software design is complex. There are many patterns, solutions and different ways for doing something. You can talk about Factory Methods, Reactive Programming or using a Service Mesh. The topics are endless.
But whatever you do: Get the types and names right!
Use good names
Using proper names is true for your source code, you config files, your database tables, your Kafka schemas and everything else.
In source code, your types, methods, variables, modules, etc, all need clear names. Mess this up and your code, architecture and deployments will be a maintenance nightmare.
Let’s look at this example (the examples in this post use Rust but the ideas apply to other languages as well).
let mut str = ""; // later assign something to tmp
Code like this is common but in most cases it is the first step towards a hard-to-maintain piece of crap. “str” can mean anything. You can store a name in it, a URL or the whole Lord of the Rings book.
Anyone reading this will have a hard time figuring out what the programmer meant.
Even worse is this made-up example (made-up does not mean I haven’t seen something similar, you probably have too).
let mut tmp: String = load_first_name_from_database(user_id); // do some stuff with tmp tmp = load_book_title_from_database(book_id); // do more stuff with tmp
That is seriously messed up. Here the programmer uses the same variable to hold completely different values. This is very confusing, particularly if this method is doing a lot of stuff and contains a lot of code (thereby also violating the Single Responsibility Principle).
The programmer should have used 2 different variable, one called “firstname”, the other “book_title”.
Even better, also use a proper type for UserID and BookID.
Use the correct type
When you define a variable, give it the correct type.
This is wrong:
let timeout_in_seconds: String = "10";
Here we have used a good name for the variable but it’s still confusing because the type doesn’t make sense.
A timeout (e.g. for opening a socket connection) is a number (seconds, milliseconds, etc). It may be a string in a configuration file but inside your program it should be a number (immediately converted to a number type when read from the configuration file).
In Rust it could be an
i32 integer like this:
let timeout_in_seconds: i32 = 10;
Careful with type inference
Many languages support type inference including Rust, Swift, Scala, Kotlin, Java (the “var” keyword was introduced with Java 10) and modern versions of C++ (C++11 or newer).
let ip_v4_address = "www.rust-lang.org";
Here we name a variable
ip_v4_address indicating that it holds an IPV4 address but we assign a String (a domain name). Rust is a very strongly and statically typed language. It also supports inferring the type when a value is assigned to a variable. This can be dangerous.
In this case type inference doesn’t help because the Rust compiler will make
&str type (a String allocated on the stack).
That code may not compile a bit further down if you use
ip_v4_address in a function call that expects an
Ipv4Addr type (part of the Rust standard library).
If you want to be sure to catch errors like this during compile time, it is best to write it like this:
let ip_v4_address: Ipv4Addr = "www.rust-lang.org";
This will not compile because the types don’t match.
The compiler is your friend but it cannot look into your mind.
Use type inferences carefully if your language of choice offers it.
Naming things and getting the types right is hard but definitely worth the effort.
Not only will your co-workers be happy when they have to read your code but you will be, too, if you have to touch it a few month after writing it.
Using good names never stops. Sometimes the meaning of a variable or struct changes slightly. Make sure you update the names or introduce a different variable or type.
Sometimes your first choice for a good name was not the best. This is like writing. The first version of a text is rarely the final one (hopefully!).
Go over your code, config files or UML diagrams and check the names!
There are many ideas and patterns for designing great software. All of them have their merits and we as developers need to learn about them.
But in my opinion, almost everything rests on using good names.