Code style and moral absolutes

In my previous post about my basic setup for solving competitive programming problems with Haskell, I (somewhat provocatively) used lists to represent pairs, and wrote a partial function to process them. Commenter Yom responded with a proposed alternative that was (less) partial. I was glad for the comment, because it gave me a good opportunity to think more about why I wrote the code in the way I did, and how it fits into larger issues of good coding practices and the reasons behind them.

Good code style as moral behavior

What is good code style? You probably have some opinions about this. In fact, I’m willing to bet you might even have some very strong opinions about this; I know I do. Whether consciously or not, we tend to frame good coding practices as a moral issue. Following good coding practices makes us feel virtuous; ignoring them makes us feel guilty. I can guess that this is why Yom said “I don’t think I could bring myself to be satisfied with partial functions” [emphasis added]. And this is why we say “good code style”, not “optimal” or “rational” or “best practice” code style.

Why is this? Partly, it is just human: we like to have right and wrong ways to do everything (load the dishwasher, enforce grammar “rules”, use a text editor, etc.), and we naturally create and enforce community standards via subtle and not-so-subtle social cues. In the case of coding practices, I think we also sometimes do it consciously and explicitly, because the benefits can be unintuitive or only manifest in the long term. So the only way to get our students—or ourselves—to follow practices that are in our rational self-interest is by framing them in moral terms; rational arguments do not work in and of themselves. For example, I cannot get my students to write good comments by explaining to them how it will be beneficial to them in the future. It seems obvious to them that they will remember perfectly how their code works in the future, so any argument claiming the opposite falls on deaf ears. The only way to get them to write comments is to make it a moral issue: they should feel bad (i.e. lose points, lose respect, feel like they are “taking shortcuts”) if they don’t. Of course I do this “for their own good”: I trust that in the future they will come to appreciate this ingrained behavior on its own merits.

The problem is that things framed in moral terms become absolutes, and it is then difficult for us to assess them rationally. My students will never be able to write a [function without comments, partial function, goto statement, …] without feeling bad about it, and they probably won’t stop to think about why.

Good code style as rational behavior

I ask again: what is good code style—and why? I have identified a few reasons for various “good” coding practices. Ultimately, we want our code to have properties such as:

  • Robustness: it should handle unexpected or invalid inputs gracefully.
  • Readability: it should be easy for others (or us in the future) to read and understand the program.
  • Maintainability: it should be easy to modify the program as requirements change.
  • Efficiency: in general, programs should not do anything obviously redundant, or use data structures with a lot of overhead when faster ones are available (e.g. String vs Text or ByteString).

Even in scenarios where one might initially think these properties are not needed (e.g. writing a one-off script for some sysadmin or data processing task), they often end up being important anyway (e.g. that one-off script gets copied and mutated until it becomes a key piece of some production system). And this is exactly one of the reasons for framing good coding style in moral terms! I won’t write comments or use good function decomposition in my one-off script just because I know, rationally, that it might end up in a production system someday. (I “know” that this particular script really is just a one-off script!) But I just might follow good coding practices anyway if I feel bad about not doing it (e.g. I would feel ashamed if other people saw it).

It seems to me that most things we would typically think of as good code style are geared towards producing code with some or all of the above properties (and perhaps some other properties as well), and most scenarios in which code is being written really do benefit from these properties.

“Good” code style is context-dependent

But what if there was a scenario where these properties are actually, concretely of no benefit? As you can probably guess, I would argue that competitive programming is one such scenario:

  • Robustness: we do not care what our program does when given unexpected or invalid inputs, since we are absolutely, 100% guaranteed that our program will only ever be run on inputs that exactly follow the given specification.
  • Maintainability: the requirements for our program will never change.
  • Efficiency: if you haven’t done much competitive programming you might be surprised to learn that we often don’t care about efficiency either. That is, although we certainly do care about asymptotic efficiency, i.e. choosing a good algorithm, problem time limits are typically set in such a way that constant factors don’t matter very much. A program that runs within 5x-10x of the optimal speed will often fit comfortably within the time limit.

So what do we care about?

  • Readability: the one thing from my previous list that we do care about is readability. Debugging becomes quite difficult if you can’t read and understand the code you wrote (this becomes even more important if you’re working on a team). And insofar as a solution represents particular insights or techniques, you may want be able to read it much later in order to remember or share what you learned.
  • Programmer time: programmer time is always valuable, of course, but with competitive programming this is taken to an extreme: it is almost always done under time pressure, so the whole point is to write a program to solve a given problem as fast as possible.

The combination of optimizing for speed and not caring about things like robustness, maintainability, and efficiency leads to a number of “best practices” for competitive programming that fly in the face of typical standards. For example:

  • Adding code to deal gracefully with inputs that don’t follow the specification would just be a waste of time (a cardinal sin in this context!). My Haskell solutions are full of calls to partial functions like read, head, tail, fromJust, and so on, even though I would almost never use these functions in other contexts. This is also why I used a partial function that was only defined on lists of length two in my previous post (though as I argue in a comment, perhaps it’s not so much that the function is partial as that its type is too big).
  • I often just use String for text processing, even though something like Text or ByteString (depending on the scenario) would be faster or more robust. (The exception is problems with a large amount of I/O, when the overhead of String really does become a problem; more on this in a future post.)
  • Other than the simplest uses of foldr, foldl', and scanl, I don’t bother with generic recursion schemes; I tend to just write lots of explicit recursion, which I find quicker to write and easier to debug.

There are similar things I do in Java as well. It has taken me quite a while to become comfortable with these things and stop feeling bad about them, and I think I finally understand why.

I’m not sure I really have a main point, other than to encourage you to consider your coding practices, and why you consider certain practices to be good or bad (and whether it depends on the context!).

Next time, back to your regularly scheduled competitive programming tips!


About Brent

Associate Professor of Computer Science at Hendrix College. Functional programmer, mathematician, teacher, pianist, follower of Jesus.
This entry was posted in haskell and tagged , , . Bookmark the permalink.

9 Responses to Code style and moral absolutes

  1. sn0wleopard says:

    I’ve been doing competitive programming for about 20+ years, and I was always a bit guilty about the code I write. Not any more! :-)

  2. Yom says:

    Well, I’m not familiar with competitive programming.

    Your point on moral is very interesting. From what I have learned in my amateur study of cognitive neurosciences, it seems that emotion and logic are not necessarily opposed the way they are portrayed in Star Trek with the character of Mr Spock. They both serve an (evolutive) purpose.

    Logic is – indeed – the tool to find suitable solutions. But suitable to what?

    That is where emotions come in play. Emotions are here to give a motivation to act. Pure logic beings would never act in any way, which would fail to demonstrate their proficiency in logic. That would be like Haskell “programs” with no side effect at all.

    “Moral” is probably a way to get emotive with software. This drive to write **better** code (hoping it will be **good** code) is probably what led most of us to Haskell at all.

    But it is still worth questioning if it is always appropriate or when it is.

  3. Pingback: What Makes for Good Coding Style – Curated SQL

  4. Using perscriptive, “moral” rules for code style can at least make sure that someone won’t be writing the worst possible code, but it will also generally prevent them from writing the best. Code formatting is a classic example: I know quite a number of developers who insist that all code should just be run, unthinkingly, through a formatter with a specific set of rules and that’s the format. It’s become more important to them that the formatting follow a specific set of easily-definable rules than it be easy to read, which is putting the cart before the horse. I can invariably find examples from real code in a project where the formatter takes code that was easier to read and makes it harder to read, but they seem to accept that as a worthwhile cost of “consistent formatting.”

    This probably has a knock-on effect on overall software quality (outside of formatting), too. After all, if someone’s not willing to think for a few minutes about spacing or what goes on which line in order to best express what they’re trying to do, what are the chances they’re going to do the harder work of shaping their data structures and algorithms to best express what they want to do? (Well, mostly what they “want to do” is get a project manager to check off a box in his Excel spreadsheet, I suppose.)

    When you take (natural language) writing courses, they teach you to write for your audience. You’re expected to write differently when you’re writing an article for a newpaper than when you’re writing an opinion piece, or an encyclopedia entry, or an essay for a magazine. The same is true of computer programming: you should be changing your style as needed for the readers of your code. The same algorithm gets expressed in completely different ways when you’re writing it to explain Haskell to beginners than when you’re writing it as part of an application being worked on by a full-time team of professional programmers. Nobody would consider changing “y := x + 3” to “ADD 3 TO x GIVING y” (yes, that’s real COBOL code!) to be a reasonable application of the “write it in plain English” rule (at least, not since “+” changed from “unknown weird symbol” to “every kid learns this convention in elementary school”), but applying this thinking elsewhere seems difficult to many.

    If you want to teach your students why we want to write code in particular ways and not others, I’d suggest trying to come up with exercises that demonstrate the problem to them and get them thinking about how to make their code clearly communicate, as well as just “work.” Perhaps you could take an individual programming exercise that everyone’s been assigned and pass out each student’s solution to another student: without talking with the original coder the student has to write a paragraph or two explaining how that other student’s solution works. (It might be even better if they solutions they’re looking at are exercises from another class.)

    I don’t write good code becuase I’ve learned a bunch of rules: I write good code because I’ve had more than two decades of experience of reading other people’s code and seeing an enormous number of situations where it was easier or harder to read.

    • Oh, and regarding the particular situation about using partial functions: it can help if you put in a bit of documentation about why believe it’s ok for this to be partial. In fact, make it “executable documentation” so that if it breaks, it’s clear immediately, even without reading the code, what assumption that was violated. In your particular example you can just add one line to the end of your definition of ‘solve’:

      solve _ = error “The specification says that inputs are only ever pairs of integers.”

      This immediately leads whoever encounters the problem in whichever direction is appropriate, whether that be fixing the input or fixing the program. This is a technique I use all the time in commercial programming.

  5. Pingback: Competitive Programming in Haskell: reading large inputs with ByteString | blog :: Brent -> [String]

  6. Pingback: Competitive programming in Haskell: summer series | blog :: Brent -> [String]

  7. Pingback: Competitive programming in Haskell: building unordered trees | blog :: Brent -> [String]

Leave a Reply

Fill in your details below or click an icon to log in: Logo

You are commenting using your account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s

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