back

by jeffreyrogers·11y ago·view on hn ↗
I find that a lot of what is taught as the "right way" to make maintainable code isn't actually that effective in practice. Particularly when I need to read someone else's code.

For example, the conventional wisdom that functions should be less than a page of text and that they should do one thing and as abstracted as possible actually makes it quite hard to understand what a program does when you aren't familiar with it.

Instead, I'd propose first writing straight line code, and then abstracting out the common functionality into separate functions as necessary. Casey Muratori (the guy behind Handmade Hero) calls this compression oriented programming [1].

[1]: http://mollyrocket.com/casey/stream_0019.html

3 comments
> For example, the conventional wisdom that functions should be less than a page of text and that they should do one thing and as abstracted as possible actually makes it quite hard to understand what a program does when you aren't familiar with it.

It does? Maybe you mean something more extreme when you say "as abstracted as possible" but for /reading code/, I generally find it easier to get the high-level picture and avoid getting bogged down in minute from code that looks like this:

  function sendReport(params) {
    var dataSource = findDataSource(params);
    var data = dataSource.generateData();
    var report = reportBuilder.build(params);
    sendReportEmail(report, param.recipients, this.template);
  }
... versus a flatter one-page implementation with 15 lines of data loading, 20 lines of report, 5 lines of email templating and 15 lines of communicating with the mail server... all sprinkled with variously scoped try..catch blocks. It also helps me focus in on the part of the task I'm really interested in without trying to load the entire system in my head.

For /writing code/, I do agree the reverse is often best: start by doing the simplest thing that works, and then refactor it into something cleaner.

I think that if functionality is repeated it makes sense to abstract it into a function. However, I've recently been reading through a codebase in which every function is about 10 lines long, and there are numerous functions that are only used once. This makes it hard for me to follow the code since I frequently need to chase down the 10 line function that is only called once to see what it is doing and then return to wherever I was in the code.
I don't think I agree with that suggestion. That's like saying a table of contents is not useful if don't read a book multiple times. Certainly the DRY principle provides a good prompt of when to abstract code but it's not the only reason to do it.

Usually I am only interested in specific parts at any given time, either because I only have a particular task to accomplish (e.g. update the email template) or because trying to understand everyone at once would overload my little monkey brain.

If I'm trying to just update the email template then I don't care about the part the loads the data and I'd be wasting my time reading it. Sometimes you can't even tell if an chunk of inline code is relevant without spending a decent bit of energy figuring out what it's doing. Having everything in a single function also makes it easier for lines of unrelated code to become entangled together and thus harder to understand.

Well-structured code gives me a choice; I dive in and see the details if I'm interested, or I can leave it for another day and focus my attention elsewhere.

Certainly though, good tools like IDE are vitally helpful in reducing the friction in peeling back the abstractions when necessary. The nature of some languages though means that the level of tool support available can vary widely, which probably has an effect on different prefered coding styles. In Java, for instance, it's pretty easy to find all the pieces of code that call a particular method, while in Python, it's often not really possible without running the code.

With good naming and comments this shouldn't really be an issue. At my most recent software internship I got to see very abstracted code with many short functions, and realized quickly how much more clear to read and maintainable it was.
Yeah, there's ways to screw up every method of coding, but I find favoring more and shorter functions tends to better outcomes. Obviously you could reductio ad absurdum any code into something like:

  function do_task() {
    do_step1();
    do_step2();
    do_step3();
  }

  function do_step {
    step1();
  }

  function do_step2 {
    step2();
  }

  function do_step3 {
    step3();
  }
But I've rarely seen that be the problem. It's been far, far more common to see giant functions that are much more understandable when broken up.
A function that is only used once is not necessarily a bad thing.

Shouldn't that 10 line function be pretty much self documenting?

Done right this should really help get a good grasp of code base quickly.

Now, if the function has bunch of side effects and you HAVE to read it to make sense of what's going on one abstraction higher, then it is done wrong.

That sounds more like a navigation problem than a problem with the code (unless the function names are misleading).
I agree somewhat, but context switching has non-zero overhead since it requires that I keep in my short term memory what the previous code was doing as well.

Also, I probably should have been clearer above. Sometimes a short function is the right approach, particularly for something that is going to be done over and over again, but the overall goal is clarity, not short functions.

Anyways, it's not worth getting too hung up on I don't think, it seems my opinion is the minority one.

>"but the overall goal is clarity, not short functions."

I've never seen this discussion play out where the "short function" side is saying that the only goal of short functions is...short functions.

Obviously "clarity" vs "short-functions" is a no-brainer discussion. But fails to acknowledge the complexities and nuances being discussed.

> For example, the conventional wisdom that functions should be less than a page of text and that they should do one thing and as abstracted as possible actually makes it quite hard to understand what a program does when you aren't familiar with it.

This hasn't been my experience at all. Functions as small as 100 lines can absolutely destroy my comprehension to the point where I need to refactor it into smaller functions to be able to understand it. I'm dealing with one such function (at 200 lines) right now at work.

Well, let me rephrase. I can see what it's doing easily enough. I just have no idea what it's supposed to be doing, on a chunk by chunk basis, because it's all hand rolled search loops and array manipulation.

I also know the final result is incorrect.

I'm sure I'll slap my forehead and wonder why I couldn't see the problem before after I'm done refactoring it.

That's one way of doing it.

I, however, do it differently. I start from a high-level of abstraction, and work my way down. At the top level I might have a function called "ImportData". I write that call down, then move to the next line where I write "RunDataAnalysis". At that point, if I decide to move down and implement the second function, I forget about what ImportData does, how it does it, or whether or not I've implemented it.

All I know is that the data structures are populated with the "Imported" data, and proceed to code along with that assumption. Obviously if I test, it won't work, but that's for unit-tests. You do unit-tests, right? Can't do that meaningfully with giant functions, but I digress.

To me, functions are a form of "interface". It delineates responsibility and expectations between different bits of code. I'm not a chaotic and/or artistic programmer and I'm definitely not a genius wunder-programmer that can hold the entire functioning of the program I'm writing in my head at any time. What I can do is systematically break-down a problem into component pieces, negotiate their interaction, and make it solve a problem.