Code Readability vs. Code Golf

All developers aim to achieve expressiveness in their code. Which is to say we strive to ensure our code is readable and that we make efficient use of our code so that things are not repeated and the behaviors of interacting objects or functions are clear and easy to understand even for a new developer who has never previously seen the code base.

But sometimes, "expressiveness" can come at a cost. Having readable code means different things to different development teams. But a clear sign of unreadable code (no matter how much IDE screen real estate is saved) is something like this oddly nested structure of if statements.



This code is pretty objectively horrible no matter how you slice it..



If no other developers can understand your code, it won't matter how elegant your code is; it will become difficult to find people who can quickly read it, understand it, and change it. Lacking such ability to transfer the codebase knowledge can turn a great piece of software into millstone for an organization once all of the original developers have left the building.

So in comes my (*biased) opinion of code golf and how it impedes code readability. It's neat and a fun way to enhance your programming skills and knowledge of a language or framework; but it serves as a terrible template for producing readable code that your colleagues can fairly easily look at and understand, or at least debug and determine how the abstractions interact and what pieces of functionality are contained in (or spread across) which files.

Code Golf: https://code-golf.iohttps://codegolf.stackexchange.com  (have fun, you can learn lots)


ASM, clear for some developers; not most:
             global    _start
             section   .text
_start:   mov       rax, 1                    ; system call for write
             mov       rdi, 1                    ; file handle 1 is stdout
             mov       rsi, message         ; address of string to output
             mov       rdx, 13                 ; number of bytes
             syscall                               ; invoke operating system to do the write
             mov       rax, 60                 ; system call for exit
             xor       rdi, rdi                ; exit code 0
             syscall                           ; invoke operating system to exit

             section   .data
message:  db        "Hello, World", 10      ; note the newline at the end

-vs-

Java, unclear 1-liner:
public class StringStatsArray { private final String[] stats; public StringStatsArray(String[] a) { stats =
  a; } public String toString() { String ret = "{"; for (String check: stats) { ret += "\"" + check + "\", "
  ; } ret = ret.substring(0, ret.length() - 2) + "}"; return ret; } public double averageLength() { double
  sum = 0; for (String check: stats) { sum += check.length(); } return sum / (double) stats.length; } public
  int search(String target) { for (int i = 0; i < stats.length; i++) { if (stats[i].equals(target)) { return
  i; } } return -1; } public int sortStatus() { if (stats.length <= 1) { return 1; } if ((int) stats[0]
  .charAt(0) < (int) stats[1].charAt(0)) { for (int i = 0; i < stats.length - 1; i++) { if ((int) stats[i]
  .charAt(0) > (int) stats[i + 1].charAt(0)) { return 0; } } return 1; } else if ((int) stats[0].charAt(0) >
  (int) stats[1].charAt(0)) { for (int i = 0; i < stats.length - 1; i++) { if ((int) stats[i].charAt(0) <
  (int) stats[i + 1].charAt(0)) { return 0; } } return -1; } else { for (int i = 0; i < stats.length - 1; i
  ++) { if ((int) stats[i].charAt(0) < (int) stats[i + 1].charAt(0)) { for (int j = i; j < stats.length - 1;
  j++) { if ((int) stats[j].charAt(0) > (int) stats[j + 1].charAt(0)) { return 0; } } return 1; } if ((int)
  stats[i].charAt(0) > (int) stats[i + 1].charAt(0)) { for (int j = i; j < stats.length - 1; j++) { if ((int
  ) stats[j].charAt(0) < (int) stats[j + 1].charAt(0)) { return 0; } } return -1; } } return 1; } } }

-vs-

C# Minecraft client, clear code:
using (var world = JavaWorld.Connect())
{
    world.PostToChat("Hello from C# and .NET Core!");
    var originBlock = world.GetBlockType(0, 0, 0);
    world.PostToChat($"Origin block is {originBlock}.");
}



"Code formatting is about communication, and communication is the professional developer’s first order of business." -Robert C. Martin (Uncle Bob)


Even complex languages can be simplified somewhat by using logical naming conventions to identify what the entities are and what operations are doing.

Clear and consistent naming conventions of all members within the code of an application go a long way for making your code open to collaboration and/or extension by others. In much the same way people prefer different kinds of poetry, we developers prefer certain programming languages or styles (.NET vs. Java vs. NodeJS vs Python vs. C and C++; Message Queues vs. RDBMS; OO vs. FP) more or less than others. We should however, as far as possible, attempt to make our code clearly formatted and named for anyone to easily interpret. We should be as clear and consistent as possible and only comment when explaining the behavior in variable or function names alone won't suffice.

And this is all not to be confused with just good old fashioned best practices encompassed in the excellent book Clean Code by Robert Martin. Write clean code. Write understandable code with well-named members etc. And for the love of software development (esp. open-source) having a fighting chance to reach its potential in this early part of the 21st century, please do not write code in such a way that serves only to prove how clever you are or obsfuscate what is really going on for reasons unknown. "Security through obsfucation" is a real thing, but it's not a good way to manage a team code base.



Assume that the developers who inherit your code are going to need a little guidance



*true story, I was once interviewed first on the phone, and then on-site for an interesting .NET/SQL Server Dev opening with a major league baseball team. I did relatively well in the 3+ hour interview which included live coding exercises and meeting with all of my potential future colleagues. I went home that day pretty confident that I'd get the job, which in my experience is usually a sure sign that I won't get the job.

The last step for me before the hiring decision was made, arrived in the form of an email which let me know that the other final candidate had submitted a better solution than mine to the final and most difficult coding challenge but only by a couple bytes (16 or 24 bits or something) and that I was going to be given a chance to resubmit my code if I could make it smaller.

The final challenge was to demonstrate some extreme DRY by writing a program that would output the lyrics to "Old McDonald had a farm" in the smallest possible source code file. So, logically I abracted away all the individual characters and repeating character sequences and did my best to make the program as small as possible.

I declined to attempt any improvements. I honestly didn't know how to make it smaller, but more to the heart of the matter, this code golf barrier for entry made me wonder if there were other development practices within the IT department of this organization which I would not be a fit for; so it was probably mutually beneficial that I dropped the ball.

Due to a few .exe filesize bytes... I came in 2nd place and lost out on my dream contract/job. All of this because of my failure to hit a hole-in-one in that darn code golfing test.

My (C#) solution looked somewhat similiar to this:

void x(string c, string d){var a="Old MacDonald had a farm";var b=", E-I-E-I-O";var f=" a ";var g=" there";Debug.WriteLine(a+b+",");Debug.WriteLine("And on that farm he had"+f+c+b+",");Debug.WriteLine("With"+f+d+" "+d+" here and"+f+d+" "+d+g+",");Debug.WriteLine("Here"+f+d+","+g+f+d+", everywhere"+f+d+" "+d+",");Debug.WriteLine(a+b+"!");


I think the actual (C?) solution with the smallest filesize was something like this: 

o(char* x, char* y)
{
    char* f=
        "Old MacDonald had a farm, E-I-E-I-O,\n"
        "And on that farm he had a %s%.13s"
        "With a %s %s here and a %s %s there,\n"
        "Here a %s, there a %s, everywhere a %s %s,\n"
        "%.35s!\n";

    printf(f,x,f+24,y,y,y,y,y,y,y,y,f);
}

Ah well, we live and learn. Development is not a series of 1-upping and out-dueling one another with battles of cleverness. It's an ongoing communication, elaboration and collaboration experiment wherein the ultimate goal is for everyone to understand the domain problems and solutions, domain modeling for the problem abstractions and a standard domain language which removes ambiguity and enables "1000ft" knowledge for even the most tertiery of interested stakeholders.

Some areas of code (for instance the data layers or the UI or the file I/O, etc.) will invaribly be understood by some team members more or less than others. And a team architect will understand everything, though not at the low, "weeds" level of the dev who is implementing a particuliar story or piece of functionality.

Code like you might die tomorrow and someone will have to pick up everything from scratch. Enable frictionless configuration for setting up the local IDE and environment for the first time. Create a useful, concise README.md that includes everything needed to get up and debugging and an explanation of anything that is completely or somewhat non-standard. Include unit and e2e integration tests that 2x-check runtime interactions and clearly demonstrate all behaviors and interactions. 

Above all else: make your application instructions so readable and elegant that perusing the source code is not unlike reading a book and immidiately understanding and visualizing each entity and operation and what typical usage and operation looks like. It's really hard, but it is important for the future that we all get it right and continuously improve our code's understandability.

Happy developing, all.







"All the quirks and buggy behaviours we can exploit in a code-golf challenge allow us to identify with less effort code that will otherwise behave unexpectedly or cause trouble."
...
I’d argue that code-golf can be greatly beneficial as long as we keep our mind in the game and not try using it where it doesn’t belong." -Juan Cortés

Very, very true points in favor of code golfing for fun and deep exploration (even if not the default for real world implementations).


No comments: