4 ms·
It is a nice/ok code base. I find Redis' a bit better. One thing that stood out in the article was calling out the percentage of functions with fewer than X li
by sidlls 9y ago
It is a nice/ok code base. I find Redis' a bit better.
One thing that stood out in the article was calling out the percentage of functions with fewer than X lines. I don't think that is a good goal to work toward. Having functions that do one thing and do it well is, and function length can be a very rough proxy measure for that. However "doing one thing" almost always means "perform more than one step in an algorithm." I've seen too many code bases where "keep functions short" is used as an excuse to break locality by piecing functions out into smaller functions that are only ever called in one place (the function they split up). It can make code harder to read and maintain and be less efficient besides.
- chubot 9y agoEh, IMO it depends on how many locals there are more than the number of lines. Reducing the state space definitely improves readability. If you have a function with 15 locals, it could be hard to keep straight in your head, even if that function were 50 lines or so. If you can split it up into 3 functions with 5 locals each (and a small number of arguments), that is usually more readable. As long as the functions are named well and communicate (only) with params and return values, it doesn't hurt readability to split it up, even if it's only used in one place. On the other hand, I suppose you can have a 500 line function and if has few locals, then you probably don't need to split it up. The canonical example would be a function that only prints stuff to the screen, maybe based on a couple parameters. But I don't really run into those situations very much -- more often I run into the big functions that can be split up.
- saurik 9y agoI don't know. My absolutely favorite codebase ever was dpkg. It isn't as good now as it was before, but essentially they arranged so that anything "unusual" (error handling and the unwind process) happened in a side channel via setjmp and then the core of the program was in a file called processarc.c which contained one giant function that you could use to see, in order, all of the middly steps that dpkg performed. Doing it like that meant that the code read like a specification. I am highly skeptical of codebases with lots of functions that are called from only one place; I would even put that forward, along with "code duplication (not just in the small but also structurally: where you should be building higher-order primitives, including your own control structures or scaffold) as its foil (as it usually isn't OK to not have a function if it would be called twice), as core metrics for "bad code". If you have too many locals... use a block! That's what they are for :/.
- chubot 9y agoI'm definitely a fan of global coherence, but it only goes so far. I briefly worked on Rob Landley's toybox project, and he takes it to an extreme. There are good things about the project, but it will eventually fall down because adding code is an an O(n^2) algorithm. That is, every new piece of code has to be compared with all the existing code, in order to maintain global coherence. So there is a hard limit on the size of a program you can write this way. Also, new contributors are forced to understand the whole thing. This is why "local reasoning" is a desirable property in software architecture -- so the program can grow smoothly. Most programs grow badly, but you want the cost to be closer to O(n) rather than O(n^2). So these types of program eventually either rot or die -- that's why dpkg "isn't as good as it was before". As always the answer is somewhere in the middle. Neither extreme is good. You want some modularity but not too much. I guess people are disagreeing on how much there should be. I concede that modularity has a much greater cost in C than in other languages, e.g. because char* in a function parameter list tells me nothing about ownership. See the sibling comment where I was puzzling through Python interpreter source code like Py_SetPath(char *). So in C it might make a lot more sense to have huge functions because there is a cost of tracking ownership across function calls. In languages with garbage collection that's much less of a problem.
- eru 9y ago> As long as the functions are named well and communicate (only) with params and return values, it doesn't hurt readability to split it up, even if it's only used in one place. It's interesting how even in the context of C the virtues that functional programming dials up to eleves come up. (Git is similar: it's a dirty C program with lots of mutation under the hood, but when you use it you can sort-of pretend it purely functional, since the main things you care about are append-only, and even rewriting history just creates a new slightly altered copy of history, and the old one is available until the GC kicks in. Similar for the recent craze of copy-on-write in filesystems.)
- chubot 9y agoYes it's it's a common pattern, especially in distributed systems. What concern does functional programming share with distributed systems? Being disciplined about state. A distributed system is no longer a Von Neumann machine, because you don't have globally consistent state (without paying for it). Procedural languages assume you're using the Von Neumann model. Also, what does OOP share with functional programming? Being disciplined about state. When OOP is used well, you're protecting invariants on state inside classes. Basically the anti-pattern is global mutable state, which most (old) codebases still have. It works poorly for concurrency too (e.g. Python and the GIL). Here are a couple related comments which touch on OOP and FP, which a lot of people seemed to like: https://news.ycombinator.com/item?id=14523728 https://news.ycombinator.com/item?id=14523728 -- Go without globals; pure dependency injection is really functional programming; state as an explicit parameter https://news.ycombinator.com/item?id=11841893 https://news.ycombinator.com/item?id=11841893 -- there's no real advantage to expressing something like "split a string by a delimiter" in a purely functional style. Either way, you have a trivial referentially transparent function you can reuse without causing complexity in your program. You might as well do the obvious imperative thing. Also, I neglected to mention that this style actually doesn't work all that well in C. It works better in C++. Not only because C++ has classes and C doesn't, but because of char. I was recently hacking on the Python interpreter, and wondering about functions like Py_SetPath(char ) and setenv(char, char) (from libc). The problem is that I have no idea whether those functions copy their arguments or they store pointers to them. I had to go read three levels deep. So in that sense, functions in C fail miserably... they are not functions in the mathematical sense, so you can't reason about them the same way without strong conventions.
- eru 9y agoOne point to complement your argument: Breaking out a function that's only ever called once can still be a good idea: functions---especially pure ones---communicate with their environment via narrow interfaces, their arguments and return values. Compare that to inlining the same block of code: the reader will have to carefully check that the inlined block is self-contained, instead of having the syntax provide the hint. Granted, this argument mostly makes sense for pure functions, and doesn't apply nearly as often in C as in eg OCaml. Haskell's usual style is perhaps the most extreme in that regard, any individual definition only very seldom goes on for longer than one screen. (Forth people strive for a similar style of ravioli code, but I don't have the experience with that language to say much.)