Software Development

Not That Sort of Code

Following recent discussion online, I’d like to make what I consider to be an important statement about code.

The purpose of code is to express the logic of the software both to the computer and to future developers.

There are various design conventions which, if followed, speak as loudly as the code, so we often find ourselves working with commonly known blocks of design to make things more predictable.

Jumbling everything up, or making it otherwise messy or cumbersome to navigate is also a bad idea. For example, putting the world in a single function over hundreds of lines (been there done that) is essentially putting a mountain in everyone’s way for future extension.

Making code impossible to debug is also a concern, though doing special things to open it up for debugging is probably worse.

For me there are a few principles that seem to make things easier:

  • Avoid surprise
  • Single Responsibility Principle – give each thing one job to do well, which kind of implies not repeating yourself
  • Short methods – if it’s small, then you can pretty much see what it does in a heartbeat and it’s likely to be easy to give a meaningful name to

Taken to It Illogical Extremes

I read somewhere recently that if statements are evil and should be avoided. The premise of nested ifs being a poor alternative to other techniques like polymorphism (for choosing the right outcome based on a type of input) makes a lot of sense. I frequently use the Strategy Pattern in place of switch or if statements.

It’s a great thought experiment to ask how we could rewrite software without using certain statements, like if or for. Indeed, I usually end up replacing lots of if conditions around null with the use of Java Optional as I find it helps me express the thought process, rather than the moves to achieve it. E.g.

1
2
3
4
5
6
return Optional.ofNullable(someInput)
    .filter(item -> hasSomeProperty())
    .map(Item::getTheThing)
    .filter(not(Thing::isEmpty))
    .map(Thing::getOtherThing)
    .orElse(new OtherThing());

In the above pseudo code, we:

  • require a non-null input
  • check it has a particular state
  • get its sub object Thing
  • require that thing is not empty
  • navigate to its child object
  • or, if any of the above doesn’t work, drop to a default

This obviates the need for if statements and explicit boundary checks, because it’s declaring the process, which implies those exact moves at runtime.

If I’m honest, the above can sometimes be a bit oblique to read and hard to debug, but only sometimes.

If Statements are Evil

They’re not. They’re pretty simple.

Complex nested if statements, which should be the exception and even then should be avoided at nearly all costs, are pretty evil. Simple, easy to read if statements are a good idea.

And this is where the problem of extremist thinking comes in. You can write software without if. You can do something like this:

01
02
03
04
05
06
07
08
09
10
11
12
function isGood(str) {
    return str === 'Good';
}
 
const whatToDo = {
   true: () => console.log("It's a beauty!"),
   false: () => console.log("I'm feeling sad")
}
 
function reactToString(str) {
   whatToDo[isGood(str)]();
}

If you can honestly tell me that this is a better piece of code than:

1
2
3
4
5
6
7
function reactToString(str) {
    if (str === 'Good') {
        console.log("It's a beauty!");
    } else {
        console.log("I'm feeling sad");
    }
}

then you’re in a dream world!

This is a trivial example, but there’s a big code smell in the centre of it, which is the use of a map to act as an if statement, mapping true and false to what are, essentially, the clauses of an if.

Maybe we could use a ternery instead, you might argue, and you’d be right, though that’s technically an if.

If You’re Going to Use Alternative Patterns Do It Well

Don’t get me wrong, there are some good arguments to be made for replacing convention structures with something else. Look at the cute extensibility of this FizzBuzz algorithm I wrote while rising to the challenge of doing it without if:

1
2
3
4
const fizzesAndBuzzes = [
  { output: 'Fizz', when: isDivisibleBy(3) },
  { output: 'Buzz', when: isDivisibleBy(5) },
];

The above meets the criteria of explaining intent to other developers, and the code that used it to output Fizzes and Buzzes worked.

However, alternative techniques must be done elegantly and in the leanest way possible. If they’re just intellectual party tricks, then we may pay the price for them in terms of future maintenance costs, or runtime speed.

TL;DR

If you feel like your code has the effect of encrypting the behaviour of your application, then rewrite it.

No construct is evil, but all techniques can be used badly to create evil code smells.

Peer review can bring things back to an agreed sense of normal.

Published on Java Code Geeks with permission by Ashley Frieze, partner at our JCG program. See the original article here: Not That Sort of Code

Opinions expressed by Java Code Geeks contributors are their own.

Ashley Frieze

Software developer, stand-up comedian, musician, writer, jolly big cheer-monkey, skeptical thinker, Doctor Who fan, lover of fine sounds
Subscribe
Notify of
guest

This site uses Akismet to reduce spam. Learn how your comment data is processed.

0 Comments
Inline Feedbacks
View all comments
Back to top button