When I searched for "Basic Programming Skills" on the Internet, I didn't find any useful article. So here is my list of things every software developer should know and practice. Of course this is about programming languages. Code examples were taken from Java and JS (JavaScript).
A software developer translates business language into computer language. Sometimes they also do business analysis, sometimes they also play table-tennis, and all are waiting for what they call career. This Blog is for the first kind of developer, not the Jack of all trades.
- Avoid Copy & Paste Programming
- Naming Matters
- Define Local Variables Where Used
- Don't Catch Exceptions
- Minimize Dependencies
Avoid Copy & Paste Programming
Also called "Code Duplication". Copy & paste programming is the oldest and worst programmer sin. This causes days and days of repeated bug-tracking with subsequent refactoring. Copy & paste code is like a virus, it spreads everywhere.
There have been so many writings about this that I don't want to go into details. Simply don't do it, take your time to do everything once-and-only-once. Try to be DRY = "Don't Repeat Yourself". Don't be WET = "We Enjoy Typing" :-)
Bad:
class Person { public final String firstName; public final String secondName; Person() { this.firstName = null; this.secondName = null; } Person(String firstName, String secondName) { this.firstName = firstName; this.secondName = secondName; } }
Good:
class Person { public final String firstName; public final String secondName; Person() { this(null, null); } Person(String firstName, String secondName) { this.firstName = firstName; this.secondName = secondName; } }
Don't use programming languages that offer no facilities to avoid code duplication. Give yourself time to learn DRY, it's not so easy. Prefer inheritance to delegation, but avoid deep hierarchies. Avoid multiple inheritance to not become ambiguous and hard to understand, keep it simple.
Naming Matters
One of the major problems you find in source code written by newcomers is bad naming.
The saying of "Doesn't matter how this function is called" has been around for decades. Wrong. All other developers should be able to understand your solutions. Naming builds onto common sense, and teams that lack it will name badly.
Another one is "Nobody except me will maintain this code". Wrong, the times when a single person could maintain a software project are long gone. Moreover I've seen lots of developers that couldn't understand their own code after some time.
Next is abbreviations. In old times it was justified by memory economy, but we've been living in times of Gigabytes for long now, so there's no more justification for using abbreviations.
The biggest problem is that names are chosen too abstract.
Functions like process()
or doit()
take over
as soon as the problem deviates from business logic into purely technical issues.
It is like losing reality, deep inside the problem,
not being able to communicate any more: "Nobody knows the trouble I've seen":-(
Bad:
public void process(String s) { .... }
Good:
public void interpretXmlContent(String filePath) { .... }
Rules:
- Name as explicitly and precisely as possible
- Consult dictionaries
- Use abbreviations only when they are unambiguous and common sense (like "DVD" or "PNG", but not "OS" which stands for both "operating system" and "open source")
- If getting too long, move the name to context by creating a wrapper class or a sub-package
- Use same names for same logic (use function overloading)
- Do not use programming languages that restrict you in any of these ways (e.g. JS misses function overloading)
Bonus: if you're good in naming, you won't need to document so much.
Define Local Variables Where Used
Another programmer habit that has been around for decades, called variable hoisting: "On top I define all variables I'll need below". Wrong, this pushes refactoring ("Extract Method") to the brink of impossibility.
Bad:
function interpretXmlContent(xmlContent) { var i, header, fragment; for (i = 0; i < xmlContent.size(); i++) { header = interpretHeader(xmlContent); fragment = interpretFragment(xmlContent); .... } };
Good:
function interpretXmlContent(xmlContent) { for (let i = 0; i < xmlContent.size(); i++) { let header = interpretHeader(xmlContent); let fragment = interpretFragment(xmlContent); .... } };
Rules:
- Define local variables where actually needed, not necessarily all on top of the function or class
- Do not reuse variables, make them constants and instead define new variables with names that express the new semantic
- Prefer function parameters to class fields (minimize object state)
- Do not use programming languages that restrict you in any of these ways
(e.g. old JS
var
hoisting, not supporting block-scoped variables)
Take note: in reality the smallest unit developers pack their solutions into
is not the function() { ... }
, it is the { block }!
Thus such parts should be later extractable as separate functions.
In the example above it is the loop body I may want to extract.
Don't Catch Exceptions
As soon as you get to know exceptions, you will be seduced to use it for implementing application logic. Result is code that is full of useless try-catch clauses, suppressing any trace of any bug. An exception is an error indicator and needs to be seen on the logging console, including all of its stack-trace.
Bad:
public void playSound(Sound sound) { if (sound == null) throw new IllegalArgumentException("Can not play null sound!)" .... } public void playAllSounds(List<Sound> sounds) { for (Sound sound : sounds) { try { playSound(sound); } catch (Exception e) { log.error("We don't like exceptions so we log them where nobody looks: "+e); } } }
Good:
public void playSound(Sound sound) { if (sound == null) throw new IllegalArgumentException("Can not play null sound!)" .... } public void playAllSounds(List<Sound> sounds) { for (Sound sound : sounds) { playSound(sound); } }
Rules:
- Don't implement application logic by throwing and catching exceptions
- Re-throw any exception that you catch for some reason, maybe nested into an exception that represents the current software layer
- Use
RuntimeException
to avoid declaring checked exceptions everywhere
Minimize Dependencies
This is about the number of imports, fields and methods in a class, the number method parameters and calls made from a method. For Java, this could be extended to the number of classes in a packages, and packages in a module.
Small is beautiful. The smaller the unit of work, the more it will be reusable. A high number of dependencies makes any source code short lived. Of course it is not possible without dependencies, but we should try to reduce them.
Bad:
class Rectangle { public void drawRectangle( int x, int y, int width, int height, int borderThickness, Color foreground, Color background) { .... } }
Good:
class Rectangle { private final int borderThickness; private final Color foreground; private final Color background; public Rectangle( int borderThickness, Color foreground, Color background) { this.borderThickness = borderThickness; this.foreground = foreground; this.background = background; } public void drawRectangle( int x, int y, int width, int height) { .... } }
Here I don't prefer parameters to class fields, because the high number of parameters demands a context.
Rules:
- A Java class with hundreds of imports is most likely a God Class or a candidate for Shotgun Surgery
- A function with 20 parameters is a mess, it should not be more than five; wrap a class around such a method, and put the immutable parameter part into constructor
- Try to write classes with less than 400 lines
- and functions with no more than one loop, the loop body being in a separate function
The smaller classes and methods are, the more we will need good naming. Software heavily depends on it.
Conclusion
This was a morale lecture. Badly needed in times where everybody tells morale being replaced by money. Don't insist on freedom when writing code, this is the last thing we want if we're going for common sense.