Hacker Newsnew | past | comments | ask | show | jobs | submitlogin

I have started to move in the other direction for C#. A long sequence of code that flows in one direction should be kept in one method if possible. Good reasons to break up a method are things that are not readability but other things such as re-use testability. If the methods are only called in one place and not performing a piece of work that is testable, then they shouldn’t be methods.

But

   DoAll() {
     DoA();
     DoB();
     DoC();
   }
Is simply not better in any way than

    DoAll() {
      // A
      ...
      // B
      ...
      // C
      ...
    }
Private methods will blur this somewhat. The key to readability is not having to jump around. Ideally of course methods are both short and reusable, readable without jumping etc, but given the choice between a 100 line method or the same code broken into 10 methods of 15 lines each (yes, more lines), where each method is only called once from a main method, I’d rather read the first. The 100 lines might indicate some other problem with the api (a chatty Dx12/win32-like API for example).

This obviously varies from case to case, I think one of the worse things a team can do is enforce hard rules that invariably lead to “style rule-induced damage”.



My two cents: write long procedures, write short functions.

It is not very useful to break up long sequences of imperative commands (procedures) because chances are the procedure chunks you end up with are completely ad-hoc and non-reusable anyway. Also, as John Carmack puts it, "you should be made constantly aware of the full horror of what you are doing".

For pure functions, it's often a bit of a smell if they're very long; usually you can decompose them into smaller functions which are actually reusable, and you end up with small units that can be tested in isolation and are very easy to understand.

Generally speaking (not a hard rule), it's a win to have more of your program written as pure functions. But it cannot always be done in a sane way, in that case, write procedures and write them long.


Bertrand Meyer, one of the original OOAD people, suggested a step farther: split decisions from actions.

I really came to appreciate this while ramping up on test writing. Decisions are often trivial to decompose, reuse, and test. Actions not so much, and are sometimes defined by procedures in the literal sense.

Still, if you have facility with the English language or aren’t ashamed to use a thesaurus, meaningful method names and boundaries can help break a problem down and let someone zero in on a bug.


I like your distinction here. That fits well with how I find myself structuring code.


I am entirely in this camp as well. I have an extremely hard time dealing with the concept of a hard line/column number limit, because everything is a grey area to me. There are cases that are obvious to most, but when you say that 100% of your codebase must comply with some arbitrary restrictions, you start to cause way more harm than good.

The approach of putting all of the logic inline into the same method is much preferable to me over the alternatives. The point at which I would turn a new method is when a block of code is executed the same way in 2 or more contexts within the same class. Otherwise, I see no reason to break out the code, unless it is making readability difficult in a fairly obvious way - e.g. an inline new List<MyParameter>() which is 100 lines long should really be under a GetMyParameters() private convenience method. Other candidates could be similar points of control (e.g. make it easier to find where to control parameter listings by having them in their own named methods), or managing wildly different levels of abstraction (mapping code vs business logic, maybe create separate mapping methods).

I've taken this a step further and started collapsing entire layers. For instance, we'd have a Repository and Service abstraction, but what ultimately happened is there was only ever 1 concrete implementation of each, so these ended up combined. Something like MyBusinessService+MyBusinessRepository were reduced to a combined MyBusinessService. This has the advantage of allowing you to see exactly how the business logic is manipulating the underlying datastore by reading a single consolidated method. The combined implementation is certainly larger overall, but taking out the context switch far outweighs this minor downside in practice.

Productivity is king. I have given up on trying to have code that looks clean simply for the sake of aesthetics.


Upvoted for collapsing spurious layers. I've started doing this, too. Why am I writing code to allow an alternative database engine, when I'm never going to move this thing off Postgres?


One of the nice things about having a repository/store interface is that it’s easy to make an in-memory fake for testing, so you can have at least some subset of your tests that don’t need to spin up a database container/vm in order to run


Yes this is certainly a solid counterargument, but if you decompose it into its aspects I think someone could make contrary points:

1. I would always advocate for testing full-stack with the datastore in the loop. E.g. an actual API request from a test runner all the way down into a test instance of Postgres. Isolating your testing to specific layers is clever, but I find 99% of my errors come from forgetting to adjust SQL queries after migrations were performed, or other things that are not going to get caught in the BL layer. If you did your BL correctly using the latest C# 8.0 primitives, you get a lot of compile-time guarantees of correctness (e.g. null enforcement throughout), so the value of unit testing these layers is diminished from my perspective.

2. The value of architectural abstractions changes over time. I would certainly advocate for a temporary IRepository injected into a service implementation if it makes the initial 100 iterations of the code faster by way of using a LINQ-to-objects shim in place of Postgres. But, once this code has been running stable in production for months without concern, perhaps its time to take out these abstractions so that the people responsible for maintaining the code on a daily basis have less complexity to deal with.


I very much disagree. The in-lined version can easily be confusing, harder to change, harder to test and even error prone.

By splitting code into functions you have a more well defined scope for each of them and they can be understood in isolation.

You also get the benefit of getting a quicker overview (if you care to name your functions well) in your calling function (DoAll).

Then there is the issue of maintainability. When I have to change something in a big (in-lined) function then I'm much more anxious about the whole context. My instinct is often to refactor it into smaller pieces so I can narrow down the changes and isolate them correctly and exactly from the context.

This might very well be a thing of preference?


I don't think it's a preference issue. The problem you have when you split up functions is you're making decisions that have consequences in a fairly arbitrary way(not backed by an understanding of the system, just how you feel things should be broken up).

On top of arbitrarily pushing the system in directions, you yourself note that you hide the actual context of the code when you split it up. It might make you feel better in the moment but I don't think it's the right response to feel emboldened by making decisions with less context. Premature abstraction is at the heart of a lot of bad design and complexity.

With even primitive dev tools you can get a lot of the benefits (grouping, naming) with comments and braces. More could definitely be done on this front but dev tool progress is sadly pretty slow. Going this route you can have organization while not throwing away the all-important context of what the code is actually meant to accomplish.


I suspect this is mostly preference.

Personally, I really dislike pulling everything out into small methods - it literally forces you to remember a crap load of names. I find remembering names to be extremely high cognitive load.

Worse, the names WILL lie to you. Especially if you weren't the one doing the naming.

So not only are you forced to remember a bunch of labels that better fit someone else's mental model, you're still not off the hook for understanding what the code in those tiny methods is doing, and considering how it might impact the task at hand.

So now you're stuck with twice the work - Remembering which name goes with which functional piece of the task at hand, and then remembering how that name actually accomplishes the task (the actual code).

Worse, because functions are moved out of line, I find it much harder to jump between relevant bits of actual "doing things" code.

Basically - The ONLY time I want to split code off into named chunks is when the alternative is copying/pasting code somewhere. I look for code which is getting reused and break that out.

The in-lined version is faster to read, faster to understand (and by understand I mean REALLY understand, not some hand-wavey "I trust this name" understand, but as in you actually know the operations and changes to the system that the call will result in)

The downside to inline code is you have to actually read it. I find a lot of the folks who really like short methods struggle to parse the language they're working in and fall back on the name without actually understand what the chunk of code does. But I think they're also the folks who have a better memory for names.


Reducing cognitive load by breaking things into digestible pieces can be hard, and at times it's hard to say if option A is better than option B, but it is not preference.

If `extractId` is an eight-step process that's only used once, but I step over it and check locals to find that the variable was correct before the call, and wrong after, then I just found my problem. That would be faster than stepping over each step to find the problem -- basically the debugging version of divide and conquer (for finding the solution, optimal would be binary search, but for readability and maintainability, that should not be the goal).

Another example is if I have a boolean condition that incorporates extremely complex logic but is only used once, if I assign it to a variable called `nameWasPreviouslySet`, that is easier to read and understand, and if I'm debugging and expect `nameWasPreviouslySet == true` at this point, and it's not, then I've figured out the bug is in setting `nameWasPreviouslySet`. So DRY shouldn't be the only reason to refactor -- refactor for readability also.


Ok, so lets take your example at face value. You have an extremely complex step of code that you only call once.

Already, you have invariants that are easy to break. You're assuming it's only called once. That's relatively safe if you're inline, that's bogus as soon you've broken it out into a named function.

Some other dev WILL come along and re-use your helpfully broken out code somewhere, and that invariant no longer holds.

Even assuming no one else has re-used it, what ensures that the problem is actually in that method? Particularly if that method itself calls out to many small named methods.

So you have some complex code that does something, but the "things" it does are call out to other small chunks of code.

so now you have

DoComplexThings => { DoSubThingA DoSubThingB DoSubThingC DoSubThingD DoSubThingE return }

So you're back to binary search - The problem is in there somewhere, but "in there" is actually calls out to many other functions again. Which one is broken?

And wait! DoThingD got changed at some point and will now fail if you run it before DoThingC, but there's nothing in the names that tells you that.

So you have hidden deps between chunks of code that are all trying to accomplish a single goal.

If you're debugging that, you almost certainly have to go through all the code in that method again, except you have to page back and forth in your editor to get the meaningful pieces into view. All so some OTHER dev could give it name that was meaningful to them, but likely not that helpful when you're debugging because the mental model they have of the system resulted in the bug in the first place.

---

Now, rant aside - I think we agree more than we disagree, I think good names matter, and I think there's a lot of wiggle room around when a thing should be broken out.

But if the code is trying to do a single thing, and it's not re-used, I'll take a single cohesive method over 8 tiny abstractions ANY day.


> if the code is trying to do a single thing

What is a single thing? Login? Initialize db? One line of code, 3 lines of code, ...

If you have 100 lines of code and 6 nestings, even if none of that is or, as far as you can tell, should ever be re-used, you should break that into smaller, digestible chunks of DoSubThingA, etc.

It's kind of an aside, but you mentioned e.g. `DoThingD` got changed or "some other dev WILL come along" -- if some other dev comes along and doesn't bother to check where and in what context `DoThingD` is being used, and changes it in a way that breaks `DoThingC` or `DoThingE`, they aren't doing their job. Particularly, if `DoThingD` wasn't written to be re-used, and they just use re-write it and use, particularly in a way that breaks its original invariance... I would be having strong words with that developer.


Oh, and I'll add in a separate comment because it's sort of unrelated. Code structure should have very little to do with how you narrow down failing code.

You can do a binary search by just shoving some log statements into the application and seeing what prints out (or hell, use the debugger for your toolset). That doesn't change if I have 100 lines in a single method, or 500 lines divided out into 100 different "tiny" methods.

I think it's actually harder when the methods are all split, because again - more paging back and forth in the editor to add the required debugging info.


You are talking about what is (or is equivalent to) printf-style debugging; if you use an actual debugger, you can step over function calls, you’ve got to do more work manually setting breakpoints to do the equivalent with undifferentiated streams of code in a long function.


So, you add a command to your debugger that lets you step over a "block" (a sequence of statements surrounded by braces -- the analog to Lisp's PROGN and Scheme's BEGIN) and before you debug the undifferentiated stream of many statements, you factor it into a handful of statements -- without introducing any new function names -- some of which are blocks.


No, what I'm talking about is how to narrow a problem down. The medium you use to accomplish that is flexible.

I'm a little confused as you why you think clicking step-over 20 times to get out of the std::lib is better than just moving the mouse down 20 lines and adding another break.

So again, this feels like we're back at "this is a matter of preference".


I kind of agree with you. If fold markers were universal, I'd say that they would have been the best of both worlds. Longer linear functions with logical blocks foldable.

Folds get a bad rap because they aren't that widespread, outside of Emacs/Vim and amusingly, the .NET world, and because some people abuse them to write God-classes and such.


I understand what you mean. I think a nice middleground between our preferences would be scoped blocks with explicitly passed vars. This would keep the readability you want but still have the other benefits I mentioned. Plus it would be simpler to refactor when needed.


This is a good point, and nicely matches a pattern I find myself using in JS where I just define the sub methods inline in the larger method.

You get the niceties of a name for the folks who want it, but the code is still grouped nicely and it's easy to walk through the whole operation.

I see this much less often in languages where scoping is less flexible though.


Personally I find all those function calls harder to debug/review. I am perfectly capable of interpreting most non-complex small code blocks with a glance. If someone takes that and replaces with a function it usually forces me to step into function to understand what it really does. Function name wont be as meaningful as the actual code.

Forcing stuff to sub functions just hiding information away from the people that reads code.


If you’re talking about scopes of variables then if language supports it, the best of both worlds would be to have an inlined version where each block has its own scope so that you can easily track which variables are actually shared among those scopes and which only belong to certain scopes.


As long as the sub-procedure don't need 10+ input parameters and 3+ output parameters/return values I can agree. Otherwise it's a sign that the code/logic simply too interrelated to be broken up well.


Just my two cents. If your A, B, C are neatly separated, then good. But it's way too easy (especially for a "quick hack" type of programmers) to introduce hidden dependencies like a goto from the middle of A to the middle of B, or C depending on a variable that was implied to be internal to B. Having separate functions helps to avoid some of such accidental dependencies. But, of course, the functions can still "communicate" via the global state or a shared state in a mutable object.


How many times has it turned into this:

DoA(DoAllContext ctx);


That tells me that DoAllContext should be a class itself that doA belongs to, or that DoAllContext should be on the class itself, and the method itself should be the class.

This is, of course, when we're talking about programming in Objects.


When I see a 100 line function it almost invariably is doing stuff at very different levels of abstraction. It's making DB calls here and API calls there; it's handling flags to indicate the end of iteration; is dealing with some user set configuration there. Teasing these things out of the code can make it much clearer what's going on.


Pretty sure VBprogrammer knows what they're talking about here....


Meh. I've used the same handle online for more than 20 years. Why stop now.

For most of the last 10 I've been working in Python.


I wasn't being sarcastic, I've seen exactly what you're talking about a lot in VB. Re-read it just now, and even I thought what I wrote kind of came off sarcastic.


I get you! Apologies if what I said came across negatively.


I think it was the italics that did it.



But only because if you're writing imperative code and doing lots of mutation "you should be made constantly aware of the full horror of what you are doing."


Also when applying the mantra "Dont repeat yourself" (DRY) it can have the opposit effect; that when you are optimizing a hot loop, like a game render loop you inline everything and discover there are repetitions that can be vectorized or removed/optimized. Maybe because you applied DRY and abstracted too early or what not.


Yeah... DRY is probably the most over-used principle (likely bc it's so easy to identify). I like "AHA" (Avoid Hasty Abstractions) as a counter-balance.


DAMP (descriptive and meaningful prose) is especially useful for maintaining tests. Which is where they bulk of the time investment comes.

Literally any testing strategy can be forced to work for about 18 months. Anyone working with shorter lifetimes than that is inexperienced.


I love that quote, and that article as a whole!


I am entirely in agreement contingent upon there being some language construct which makes it possible to explicitly limit which variables from one block can be seen in subsequent blocks. For example

    DoAll() {
      // A
      a1 = ...
      a2 = ...
      a3 = ...

      withOnly a1, a3:
      // B
      b1 = f(a1)
      b2 = g(a3)
      
      withOnly b2:
      // C
      c1 = h(b2)
    }
Since this doesn't exist in any language, as far as I know, I guess I'll stick to using functions for now.


C-style languages tend to have squiggly brackets which can limit scope somewhat, but not as totally as you describe.

For example:

   doThing(){
      x = 7; //x visible in whole function
      {
         y = x*2; //y only visible in this block.
      }
      // x still visible, y no longer visible
   }
Explicitly removing things from scope doesn't seem to be a feature though.


The application i am working on right now has a 766-line setup method written like this. Each block corresponds to the setup of one component. If there is something from that block that needs to be reused (typically, the channels that the component published data to), it is declared immediately above the block.

Many small bits of logic are broken out into their own methods (checking if today is a public holiday, configuring the HTTP server, configuring the KV store client, etc), but the setup of the domain logic components is all in one method.

There's no way to split the setup of the components into methods without either introducing a comparable volume of boilerplate, or having to write some kind of framework to make it easier. Neither would be a win.

A caveat: this codebase uses a language which doesn't have out parameters or multiple-value returns. If it could use those, the boilerplate involved in breaking up the setup method would be considerably smaller. But still probably not worth it.


It does exist, through closures. The syntax in most languages doesn't improve the readability of the code though:

    DoAll() {
      // A
      a1 = ...
      a2 = ...
      a3 = ...

      // B
      b2 = lambda(x,y) {
        b1 = f(x)
        b2 = g(y)
      }(a1,a3);
      
      // C
      lambda(x) {
        c1 = h(x)
      }(b2);
    }
I've added explicit intermediates to better visualize where the scope binding happens, but most languages allow the compiler to bind these variables implicitly whenever an anonymous function references variables from outer scope. That helps with readability, but not with scoping.


But that doubly defeats the purpose.

1. If you have good reasons to restrict variable access in subsections of your long procedure, then it DOES make sense to factor them out as separate functions.

2. Closures automatically close over variables from the surrounding scope (duh), so they don't protect you from accidentally using a variable in a section that you're not supposed to use it in.


The lambda approach avoids "I can't think of what I should call this, I'll call it `foo` for now" problem...

Which isn't a problem we should be avoiding. Naming things in a way that other coders will understand is an important skill, and can be difficult.


How does it avoid that problem?

The lambda doesn't help me understand what that part actually does any more than calling the function "foo_helper" or "foo_partN" or something like that would do.


> Which isn't a problem we should be avoiding

I believe we're actually arguing the same thing.

Anonymous functions aren't the end of the world, but self-documenting code is better.

In the JS/JQuery world, breaking away from anonymous functions would represent such a fundamental shift in practice, I'm not going to do it on my own, but in Angular, I stay away from anonymous functions.


IDEs could make it readable and encourage to split code without splitting perception. A solution is: allow calls to be ▶expanded inline in the source view, as if it was written here* Renaming arguments to their caller-site counterparts would also help.

* Code folding is not the same thing, as it doesn’t eject scope+args out of the “caller” and cannot be used in expression.

Upd: I also would like to add that sources lack usual style formatting. //-sections usually should stand out, but one can only make comment bigger (2-3 lines with === filler), but not the text itself. It would be nice to have some rtf in code and css in IDE. Books are written like that, and that’s well-acknowledged way to structure instructions and descriptions.


> allow calls to be ▶expanded inline in the source view,

If I understand what you mean, Visual Studio Code allows this. I guess Visual Studio does as well.

The implementation feels a little clumsy to me, but hey, I'm the guy who thinks Netbeans and Eclipse where the best IDEs I've used, so I guess a bunch of you guys will think the solution is perfect.


That’s interesting, I thought no one does that yet. Can you please link to this feature? (Can’t figure out what keywords to search)


Right-click the method call, "Peek Definition". Or Alt-F12.


Thanks! It is close, but sadly that overlapping is not much different from just “going to”, as it hides the code behind it. Still may be better than nothing though.


It doesn't hide the code behind it actually, it pushes it down. I agree it's not perfect though.


What would a perfect solution look like, and what would it be worth?


IMO, things evolve when a function grows.

First, you have “a hundred lines of code”

Then, you add white space to separate somewhat independent parts.

Then, you add comments that describe those parts.

Then, you notice some of these comments aren’t good, and refactor the code a bit and tweak the comments to make them good comments.

Once you’re there, refactoring the parts into decent functions is just a further iteration. It may be hard work, though, as it often means introducing new types that must be given good names.

Where you stop with this is a bit a matter of taste, but also depends on project size, development team size, (expected) longevity of the code base, etc.


Keeping your procedure short still has value, even if they just do one thing after another, and are only used once.

That's because separating them out into separate procedures enforces a simple interface: communication only happens via arguments and return values.

When a reader sees a block like

    DoAll() {
      // A
      ...
      // B
      ...
      // C
      ...
    }
then they have to manually verify that the variables in A don't interfere with what's happening in B or C. (Either accidentally or by design.)

In C-style languages you can use {}-delineated blocks to get most of this benefit of the short-method style in the long-method style.

In eg Python you can use nested functions to achieve your readability goals of not having to jump around while still keeping your state from interacting.


I prefer the first one if the functions are well named. Especially if there is any potential for A, B or C to be reused by another part of code. Even with no reuse potential function names are more likely to be kept up to date than comments.


Me too. I often do that to compartmentalize. I'm not sure why anyone would prefer the second one. Now if A/b/c/d are only a few lines each, then sure go for it. AKA they fit in 24 lines ;) . I don't like hard rules but if it starts being more than 1 page long I get antsy and start refactoring my function/method. It might be 80 lines exactly but it's a demarcation point. So I'm probably more of a 100/40 person :). I would say that I use a medium size font, I'm not a tiny font person either.


I'd say the first is always better than the second, provided the DoA..DoC aren't sharing data via global variables.

The reasoning isn't so bad. Complexity is a function of length and communication. Complexity is the enemy. When you optimise for speed, the other side of the equation is always complexity. Or: if you can decrease complexity and increase speed it's always a win.

Spitting code up in your example up makes it obvious the steps are utterly independent. In fact if there are go global variables those steps can be done in any order - or indeed in parallel. Since splitting it up makes that fact self evident whereas to learn it before you had to read the code, it has reduced complexity.

But of course you almost never split it up as you have done, because there is always a tradeoff between function length and communication. Reduce one you almost always increase the other.

Worse, communication is a multi-faceted beast, so measuring its contribution to complexity is hard. Communicating by passing parameters to pure functions increases complex less that communicating by global variables for instance. And can't check communication complexity using code length - if you pass a single value "a" that is in fact just a copy of the local variables, you've achieved nothing! Using length as a measure for complexity is equally fraught. If DoA() is a single like of code, I doubt anybody would say you've improved things much. But if DoA() was, unlike your illustration, lines intermixed it lines from DoB() and DoC(), then you've made a huge impact on it.

Put it all together and there is rarely a a simple answer to the "should I split code" question. The only time there is an unambiguous "yes" when when you reduce both code length and communication.


I have this for Big Ol' Switch Statements. It's actually easier to have all the cases inline and let the function get huge than it is to have the cases call subroutines (because you then have to go find the subroutine, and you can't compare two subroutines in situ easily).

Like any rule, there are exceptions. Mostly I agree with 80x24, but I wouldn't implement it in a linter, because I need to break it every now and again (and Big Ol' Switch Statements are a common cause of breakage).


It depends on the switch statement, but I often use this style for state machines which are particularly handy to see a lot of times.


"Inlining" A, B, C is OK when they go down in abstraction about the same depth. If not, you have a mix of simple and low level code. Using A, B, and C masks this.


From a testing standpoint the former is often superior. It will also tend to focus your attention on organizing your corner cases better.

But as my peer alludes, there are a lot of people who happily write a lot of functions full of side effects and string them together. And in those hands a great mess can be made. I used to be more tolerant of this sort of code but I’ve heard enough complaints to scrutinize this pattern and it does seem to result in quite a few more errors.


In theory yes. But the first approach makes it easy to reorder steps without making silly copy-paste mistakes. It also allows you to easily add testing for each of the steps. With second approach, before you can start testing each of the substeps you'd have to put them in a separate function anyway.


I would like an IDE that can virtually inline code. That way we can write code like your top example and avoid jumping around like the second example.

There could be a + to the left and maybe a red box around the fake inline-ing.


I’ve wanted this too. Probably for most of my career. Particularly for code in other files.

Although I’ve also found that memorizing the advanced navigation features of the IDE is a pretty good way to get around without upsetting your working memory.


Microsoft Visual Studio has this. You can right-click on a function name and select "Peek Definition", and it will open an inline window with the code for that function.


Nice, it actually render the definition inline without placing it in a floating window which is the normal way this feature is implemented: https://docs.microsoft.com/en-us/visualstudio/ide/media/peek...

In gnome-shell-mode [1] we can evaluate arbitrary expression and paste the content (virtually) into the buffer. Since javascript functions evaluate to their source it makes for a poor-mans peek-definition: https://imgur.com/a/7Ot7KFy

It would maybe (sometimes) be nice to even intelligently "macro expanded" the call:

    function helper(a, b) {
      let c = a + b
      return a + c
    }
    
    function p(y) {
      let x = helper(1+y, 2)
    }
    
    --> 
    
    function p(y) {
      // inlined x = helper(1+y, 2)
      let x
      {    
        let c = (1+y) + 2
        x = (1+y) + c
      }
    }
Not the best example, but you get the idea. Expansion can be tricky when the arguments are non-trivial expression. Could fall back to assignment to a variable named after the corresponding parameter.

[1]: https://github.com/paperwm/gnome-shell-mode


VS Code has this in some of the more developed language plugins and so does eclipse (at least it does for the c++ and python plugins)


   DoA();
Is an API like thing.

   // A
   ...
Is not an API like thing.

API's are hard to get right. API's create dependencies. Most internal API's are hot garbage.


It's not an API if the methods are private or declared inside the method. I just rewrote a portion of code the other day with the discussed pattern because it made the high level step very clear:

  DoAll() {
    DoA();
    DoB();

    DoA() { ... }
    DoB() { ... }
  }


One thing I like about the "straight code" version is that it is clear that things are executed sequentially, and only one time. With named functions it is possible that they could call each other too.


I find this interesting. Your point would align with the thread a while ago about the giant kubernetes function (https://news.ycombinator.com/item?id=18772873).

I have a theory that SRP is the driver behind a lot of this, and that the way it's commonly understood is incorrect (in part because the idea itself is somewhat ambiguous and possibly incoherent). I'll try to go into more detail in a blog post.


I prefer the Python rule of 79 characters tops per line, but I lean more towards 120 characters. I think helper methods (private methods) are useful, but having a bunch of methods called one after the other like you point out seems pointless. My other rule of thumb is: DRY, if I'm repeating code, it should be a helper method. Otherwise if it's a one off method, it shouldn't be broken out unless it helps me follow the flow better.


About a year back I moved from 120 back down to 100. The rationale was that, even on a large monitor, it was difficult to fit multiple source files side-by-side at a font size I could read comfortably. On a laptop monitor, it was hopeless.

I also noticed, during code reviews, that people read and digest statements that are broken up into multiple lines much more carefully than they do compound statements that live on a single line. Leaving me to think that line width is not just an aesthetic matter. It's really a very impactful decision that can have an important, if subtle, influence on overall software quality.


> About a year back I moved from 120 back down to 100. The rationale was that, even on a large monitor, it was difficult to fit multiple source files side-by-side at a font size I could read comfortably. On a laptop monitor, it was hopeless.

It very much depends on the screen. With a 32 inch screen you can fit three 120-column views at the equivalent of 100 DPI at default zoom. Or even better, six 120x40 views.


Yeah that's one argument I have heard, I guess I try to go 79 and if it gets lost and I'm at 120 I am not gonna cry about it too much, but anything over 120 characters is pushing it. But yeah, some super long lines I try to bring back. I do know about the split screen thing since PEP-8 outlines some of these things for Python code and I've read a lot of articles about why PEP-8 makes sense.


I feel the first is better, mainly because if the functions are named appropriately, from a glance you can glean the general idea of what the function does. And with modern ide’s, I think navigating that structure is easier than scrolling through the latter.

Of course I just got stuck writing jupyter notebooks for a month so I’m against all the scrolling crud.


'A long sequence of code that flows in one direction'. I guess I agree in that case but how often does that happen? Generally a 100 line method will have loops, ifs and be quite deeply nested. In that case it should be split. The most important benefit is that you get to give the smaller blocks a, hopefully, descriptive name. That way it becomes much easier to follow what is happening.


Came here to say exactly this.




Consider applying for YC's Fall 2026 batch! Applications are open till July 27.

Guidelines | FAQ | Lists | API | Security | Legal | Apply to YC | Contact

Search: