7 ms·
There is a (large, I believe) aspect of good code that is fundamentally qualitative & almost literary. This annoys a lot of computer programmers (and academics
by recursivedoubts 2y ago
There is a (large, I believe) aspect of good code that is fundamentally qualitative & almost literary. This annoys a lot of computer programmers (and academics) who are inclined to the mathematical mindset and want quantitative answers instead.
I love dostoyevsky and wodehouse, both wrote very well, but also very differently. While I don't think coding is quite that open a playing field, I have worked on good code bases that feel very different qualitatively. It often takes me a while to "get" the style of a code base, just as a new author make take a while for me to get.
- User23 2y agoMathematicians have recognized the importance of elegance for millennia.
- zwnow 2y agoI consider code bad if it takes more then 5 seconds to read and understand the high level goal of a function. Doesn't matter how it looks. If its not possible to understand what a function accomplishes within a reasonable amount of time (without requiring hours upon hours of development experience), it's simply bad.
- ajross 2y ago> I consider code bad if it takes more then 5 seconds to read and understand the high level goal of a function. That's something that's possible only for fairly trivial logic, though. Real code needs to be built on an internal "language" reflecting its invariants and data model and that's not something you can see with a microscope. IMHO obsessive attention to microscope qualities (endless style nitpicking in code review, demands to run clang-format or whatever the tool du jour is on all submissions, style guides that limit function length, etc...) hurts and doesn't help. Good code, as the grandparent points out, is a heuristic quality and not one well-defined by rules like yours.
- zwnow 2y agoI even wrote no matter how it looks? I meant the goal of your function needs to be grasped within a reasonable amount of time. This works for every codebase.
- ajross 2y ago> I meant the goal of your function needs to be grasped within a reasonable amount of time. This works for every codebase. It really doesn't though. Here's a function of mine. It's maybe 40 lines of logic, so medium-scale. It's part of an intrusive red/black tree implementation for Zephyr. I'm fairly proud of how it turned out, and think that this code is awfully readable given its constraints. No human being is going to understand fix_extra_red() without having already read and understood the rest of the file, and coming to it with an understanding of the underlying algorithm. Certainly I can't. I can't even get started on maintaining this code that I originally wrote within a five minute time frame, it's an hour at least every time, just to remind myself how it works: https://github.com/zephyrproject-rtos/zephyr/blob/main/lib/utils/rb.c#L153 https://github.com/zephyrproject-rtos/zephyr/blob/main/lib/u... Now maybe this is "bad code", and "good code" could exist for this problem that still meets your requirements. But... if so that's an awfully celestial definition if it's so hard to find.
- sunrunner 2y agoThis is exactly the kind of example I have in my head for code that constitutes a high level of information density. Adding abstraction and 'literate' constructs to try and make things readable is ultimately deferring the fact that understanding the code here is fundamentally about understanding a specific implementation of an algorithm, and to understand _that_ ultimately needs the reader to build their own clear mental model of both the algorithm itself and how it's been translated into a specific form. Maybe it's a defeatist attitude, but I feel like sometimes the problem is the problem, and pushing abstractions only works to defer the requirements to understand it. Sometimes you can defer it enough to do useful work, other times you just need to understand the thing.
- jacobr1 2y agoThere is a call-stack depth problem here that is specific to codebases though. For one familiar with the the conventions, key data abstractions (not just data model but convention of how models are structured and relate) and key code abstractions, a well formed function is easy to understand. But someone relatively new to the codebase will need to take a bunch of time switching between levels to know what can be assumed about the state or control flow of the system in the context of when that function/subroutine is running. Better codebases avoid side-effects, but even with good separation there, non-trivial changes require strong reasoning about where to make changes in the system to avoid introducing side-effects and not just passing extra state or around all over the place. So, I'd take "good architecture" with ok and above readability, over excellent readability but "poor architecture" any day. Where architecture in this context means the broader code structure of the whole project.
- zwnow 2y agoBut who talked about bad architecture? Good readable code doesn't rule out good architecture. Surely some things are complicated but even then, a dev should be able to quickly see whats going on with minimal expertise in a codebase.
- swatcoder 2y agoThey're suggesting that readability and "5-second accessibility" are essentially contextual and build on a conceptual language that might be specific to a tool, team, or project. The novel function that might take "5 seconds to read" for the 20 people contributing to a mature project with a good architecture might nonetheless take 10 minutes for a new hire to decipher because they don't know the local vocabulary (architecture, idioms) and need to trace and interpret things elsewhere in the project and its libraries. Meanwhile, writing implementations in a way that tried to avoid a local vocabulary altogether might make naive reads easier, but less readable to experienced team member because they're not as concise as they could be. Your general advice to "make things easily readable" is good advice, but like with writing compelling prose or making accessible graphic design, you need to consider your audience and your choices might look different than the ones somebody else might make.
- sunrunner 2y agoDoes this apply to all domains and all 'kinds' of code? I feel like there's a fundamental difference in the information density between code that, for example, defines some kind of data structure (introducing a new 'shape' of data into an application) versus code that implements a known algorithm that might appear short in line length but carries a lot of information and therefore complexity.
- zwnow 2y agoIf the algorithm is well known, it's all good as long as the function name for it is somewhat understandable. I have to work with 200 line functions at work and it's a complete, excuse the language, shitshow.
- sunrunner 2y ago> as long as the function name for it is somewhat understandable But does using a function, essentially a box with known inputs and outputs, constitute actually understanding the function? What happens if you need to debug or understand the implementation of it? Now the original name has gone and you're looking at a larger number of differently-named things that hopefully communicate their intent well. But if you need to understand _those_, and so on...
- zwnow 2y agoMy original comment never was about understanding the implementation details. It was about understanding the high level goal of the function.
- andybp85 2y agofor the codebase i work on, i made a rule that "functions do what the name says and nothing else". this way if the function does too much, hopefully you feel dumb typing it and realize you should break it up.
- quinnirill 2y agoThen what does the function that calls the split functions get called? foo_and_bar_and_qoo? And if they’re called only under some conditions?
- brulard 2y agoIt likely has some higher-level meaning other than just do foo, bar, qoo. For example if you are calling functions "openDishwasher", "loadDishwasher", "closeDishwasher", "startDishwasher", your function should be called "washDishes". Not always that straightforward, but I believe in 95% it's not difficult to put a name on that. For the rest 5% you need to get creative, or maybe you realize that you didn't group the function calls well enough to have an atomic meaning.
- quinnirill 2y agoYeah, I agree in spirit but I think the answer is more ”it depends” than something where you should feel bad or something if you deviate from it. If washDishes also sends a bunch of metrics/diagnostics or updates a database of your favorite dish washing programs somewhere inside it, that’s probably fine. Otherwise you push the path of least resistance to just be vague instead, then you get a codebase full of functions with names like handle or process.
- jbeninger 2y agoI have definitely been guilty of naming functions foo_thenBarSometimes. I wince whenever I write them, but I've never really regretted seeing them later on, even after years. So sometimes it really is a perfectly good name. Sometimes there are two related functions that are often called together and don't have a succinct label for the combined operation.
- intrasight 2y ago> Doesn't matter how it looks. That's the mindset that the author is trying to counter.
- callc 2y agoGenerally agree. Consider reading kernel or driver code. These areas have a huge amount of prerequisite knowledge that - I argue - makes it OK to violate the “understand at a glance” rule of thumb.
- jayd16 2y agoSo it should take 5 minutes whether it's your language or choice or the assembly it compiles to? Or does it matter how it looks in _that_ case?
- jimmaswell 2y agoThis is what xmldoc/jsdoc/etc are for. If it's not 100% obvious from the name, put a summary of the function's assumptions, side effects, output, and possibly an example in the comment-doc. If you do this right, the next programmer will never have to read your source at all (or even navigate to your file! They'll hover over a method call or find it in the dot-autocomplete and see a little tootip with this documentation in it, and know all they need to know). It's an incredible thing when it works. It's a little bit more effort but I don't accept the FUD around "comments become out of date immediately because the code will change" etc. - that should be part of code review. https://learn.microsoft.com/en-us/dotnet/csharp/language-reference/xmldoc/ https://learn.microsoft.com/en-us/dotnet/csharp/language-ref...
- hinkley 2y agoAlways, always check in about whether it would be simpler to fix the function than to write an extended apology for it working the way it does. While Five Whys works very well for disaster prevention, I find 3 often suffice for fixing rather than explaining an architectural wart. Often we used to need this to work this way because something else had to work a particular way, but as the product grew that is no longer true, or desirable. So you might be able to fix it or put a fix on the backlog.
- fasbiner 2y agoSounds like you are content to limit yourself to problems that do not contain more irreducible complexity or require more developer context than what fits within five seconds of comprehension. That's a good rule for straightforward CRUD apps and single-purpose backend systems, but as a universal declaration, "it is simply bad" is an ex cathedra metaphysical claim from someone who has mistaken their home village for the entirety of the universe.
- hinkley 2y ago> is an ex cathedra metaphysical claim I have a cargo ship-sized suspicion that your code is difficult to read for reasons other than intrinsic complexity. You’ve found a way to explain it to yourself and excuse it to others, but you won’t always be the smartest person in the room. Also that’s not what was said. > more then 5 seconds to read and understand the high level goal of a function Understanding what something is for is not understanding how it accomplishes it.
- fasbiner 2y agoIf you find compact language above your level of proficiency confusing, you can literally ask an LLM to explain it for you to trade efficiency for accessibility. You sound unhappy and seem to be lashing out, and your username can only be read as an allusion to a mentally ill would-be assassin. Given those, you can maybe begin to understand why your opinions are not credible as a contribution even in relation to other anonymous people. A small part of your comment is salvageable, though: > Understanding what something is for is not understanding how it accomplishes it I can think of at least one area I know something about where the 5 second rule fails - sometimes when working on a shader and optimizations for it, it takes more than 5 seconds for the person who wrote the code to describe what it's for at a high level. If even the person who wrote the code can't meet that arbitrary constraint, other people looking at the code for the first time have no chance.
- bad_haircut72 2y agoDo you think you would understand every function in the doom codebase in under 5 seconds? Is this bad proframming then?
- hinkley 2y agoMaking code faster without making it more difficult to read is an art so black that some people insist it doesn’t exist. Doom is about being fast. Doom famously has a function in it so obscure that nobody remembers how they even came up with it.
- zwnow 2y agoYou did not understand my original comment... Doom might be good code but its maintainability is horrendous in modern standards.
- hinkley 2y agoCode that looks like it has a bug in it but doesn’t will draw the eye over, and over, and over again when fishing for how regressions or bugs got into the code. This is the real cost of code smells. At some point it’s cheaper for me to clean up your mess than to keep walking past it every day. But I’m going to hate you a little bit every time I do.
- louthy 2y agoI 100% agree with this. One of the best compliments I ever got (regarding programming) was from one of my principal engineers who said something along the lines of "your code reads like a story". He meant he could open a code file I had written, read from top to bottom and follow the 'narrative' in an easy way, because of how I'd ordered functions, but also how I created declarative implementations that would 'talk' to the reader. I follow the pure functional programming paradigm which I think lends itself to this more narrative style. The functions are self contained in that their dependencies/inputs are the arguments provided or other pure functions, and the outputs are entirely in the return type. This makes it incredibly easy to walk a reader through the complexity step-by-step (whereas other paradigms might have other complexities, like hidden state, for example). So, ironically, the most mathematically precise programming paradigm is also the best for the more narrative style (IMHO of course!)
- qwertygnu 2y ago> The functions are self contained in that their dependencies/inputs are the arguments provided or other pure functions and the outputs are entirely in the return type. Is this just a fancy way of saying static functions?
- louthy 2y agoNope, pure functions are referentially transparent. The key idea is that you can replace the function invocation with a value and it shouldn’t change the program. A regular static function could refer to a file, a database, or it could change some global memory, etc. So, replacing the static function (that causes side-effects) with a pure value wouldn’t result in the same program. Side-effects are usually declaratively represented by something like an IO monad. Which in reality is just a lambda with the side-effecting behaviour in the body of the lambda. So, to make a pure IO function you don’t actually perform the IO in the function, you return a data type (the lambda) that represents the IO to perform. This maintains the purity if the function and ‘passes the buck’ to the caller. In the case of Haskell, all the way up to its Main function and into its runtime — making the language itself pure, even if the runtime isn’t. This isn't just a Haskell thing though. I'll write code this way in C# (and have built a large pure-FP framework for C# to facilitate this approach [1]). Here's an example of the more 'narrative style' [2] of C# using pure-FP. It reads from top-to-bottom, keeping the related functions near each other and walking the reader through the functionality. There's also a massive removal of the usual clutter you see in C#/Java programs, getting down to the essence of the logic. It won't be to everybody's taste (as it's not idiomatic at all), but it demonstrates the idea. This style works well for regular program logic and less well for things like APIs where there's not always a narrative you can tell. [1] https://github.com/louthy/language-ext https://github.com/louthy/language-ext [2] https://github.com/louthy/language-ext/blob/main/Samples/CardGame/Game.cs https://github.com/louthy/language-ext/blob/main/Samples/Car...
- WillAdams 2y agoArguably, this is why Literate Programming (see my comment elsethread) didn't take off.
- freetonik 2y ago>This annoys a lot of computer programmers (and academics) who are inclined to the mathematical mindset and want quantitative answers instead. I find many syntactical patterns that are considered elegant to be the opposite, and not as clear as mathematics, actually. For example, the the ternary operator mentioned in the article `return n % 2 === 0 ?'Even' : 'Odd;` feels very backwards to my human brain. It's better suited for the compiler to process the syntax tree rather than a human. A human mathematician would do something like this: ⎧ 'Even' n mod 2 = 0 f(n) = ⎨ ⎩ 'Odd' n mod 2 ≠ 0 Which is super clear.
- pc86 2y agoWell of course if you have the freedom to write a mathematical expression you're going to be able to present it in a way that is clearer than if you have to type monospace characters into a text editor. I'm not sure it's realistic to expect to be able to type a mathematical expression using ascii more clearly than you can write it by hand (or implement using special unicode characters).
- cdirkx 2y agoQuite some years back I worked with JetBrains MPS which used a "projectional editor" instead of a text editor. It was pretty neat to be able to enter "code" as mathematical expressions, or even state machine tables or flow diagrams with actual nodes instead of a text representation. Sadly not much has happened in that space since then, but it was cool to think about what our tools of the future might look like. (of course ignoring all the practical reasons why we're probably still using regular text files in 100 years)
- Timwi 2y agoI understand that you might find the mathematical notation clearer but I think it's presumptuous of you to speak on behalf of all humans, or even all human mathematicians. I'm a mathematics graduate and I find the conditional operator more readable in a program because it corresponds to what the program actually does (it checks the condition first); but I also recognize that the two notations have exactly the same information content and only differ superficially in syntax, making it entirely a matter of familiarity.
- gwbas1c 2y agoThis is why code reviews are so critical: They help keep a consistent style while onboarding new team members, and they help a team keep its style (reasonably) consistent. (Also, see my comment about .editorconfig: https://news.ycombinator.com/item?id=43333011 https://news.ycombinator.com/item?id=43333011. It helps reduce discussions about style minutia in pull requests.)