| ▲ | awesan 4 days ago |
| I feel like no one serious uses the uncle Bob style of programming anymore (where each line is extracted into its own method). This was a thing for a while but anyone who's tried to fix bugs in a codebase like that knows exactly what this article is talking about. It's a constant frustration of pressing the "go to definition" key over and over, and going back and forth between separate pieces that run in sequence. I don't know how that book ever got as big as it did, all you have to do is try it to know that it's very annoying and does not help readability at all. |
|
| ▲ | WillAdams 4 days ago | parent | next [-] |
| For an example of what happens when he runs into a real programmer see: https://github.com/johnousterhout/aposd-vs-clean-code _A Philosophy of Software Design_ is an amazing and under-rated book: https://www.goodreads.com/en/book/show/39996759-a-philosophy... and one which I highly recommend and which markedly improved my code --- the other book made me question my boss's competence when it showed up on his desk, but then it was placed under a monitor as a riser which reflected his opinion of it.... |
| |
| ▲ | jcranmer 3 days ago | parent | next [-] | | That entire conversation on comments is just wildly insane. Uncle Bob outright admits that he couldn't understand the code he had written when he looked back on it for the discussion, which should be an automatic failure. But he tries to justify the failure as merely the algorithm just being sooooo complex there's no way it can be done simply. (Which, compared to the numerics routines I've been staring out, no, this is among the easiest kind of algorithm to understand) | | |
| ▲ | tptacek 2 days ago | parent [-] | | The whole thing is really uncomfortable; it's as if, after attempting the sudoku solver, Ron Jeffries sat down for a discussion with Peter Norvig, who was not especially diplomatic about the outcome of the experiment. The section before that, where they're talking about the "decomposition" of Knuth's simple primality tester, is brutal. "Looping over odd numbers is one concern; determining primality is another". |
| |
| ▲ | hyperman1 4 days ago | parent | prev | next [-] | | That's a really interesting read. I felt myself being closer to John about the small method part, but closer to UB for the TDD part, even if in both cases I was somewhere inbetween. At the very least, you convinced me to add John's book to my ever-growing reading list. | |
| ▲ | onionisafruit 4 days ago | parent | prev [-] | | Each page in that book serves its purpose. That purpose is raising the monitor 0.1mm. |
|
|
| ▲ | Scarblac 4 days ago | parent | prev | next [-] |
| Turns out writing a book and getting it published with the title "Clean Code" is great marketing. I have had so many discussions about that style where I tried to argue it wasn't actually simpler and the other side just pointed at the book. |
| |
| ▲ | bluecalm 3 days ago | parent | next [-] | | It's like with goto. Goto is useful and readable in quite a few situations but people will write arrow like if/else tree with 8 levels of indentation just to avoid it because someone somewhere said goto is evil. | | |
| ▲ | zahlman 3 days ago | parent | next [-] | | Funny how my Python code doesn't have those arrow issues. In C code, I understand some standard idioms, but I haven't really ever seen a goto I liked. (Those few people who are trying to outsmart the compiler would make a better impression on me by just showing the assembly.) IMX, people mainly defend goto in C because of memory management and other forms of resource-acquisition/cleanup problems. But really it comes across to me that they just don't want to pay more function-call overhead (risk the compiler not inlining things). Otherwise you can easily have patterns like: int get_resources_and_do_thing() {
RESOURCE_A* a = acquire_a();
int result = a ? get_other_resource_and_do_thing(a) : -1;
cleanup_a(a);
return result;
}
int get_other_resource_and_do_thing(RESOURCE_A* a) {
RESOURCE_B* b = acquire_b();
int result = b ? do_thing_with(a, b) : -2;
cleanup_b(b);
return result;
}
(I prefer for handling NULL to be the cleanup function's responsibility, as with `free()`.)Maybe sometimes you'd inline the two acquisitions; since all the business logic is elsewhere (in `do_thing_with`), the cleanup stuff is simple enough that you don't really benefit from using `goto` to express it. In the really interesting cases, `do_thing_with` could be a passed-in function pointer: int get_resources_and_do(int(*thing_to_do)(RESOURCE_A*, RESOURCE_B*)) {
RESOURCE_A* a;
RESOURCE_B* b;
int result;
a = acquire_a();
if (!a) return -1;
b = acquire_b();
if (!b) { cleanup_a(a); return -2; }
result = thing_to_do(a, b);
cleanup_b(b); cleanup_a(a);
return result;
}
And then you only write that pattern once for all the functions that need the resources.Of course, this is a contrived example, but the common uses I've seen do seem to be fairly similar. Yeah, people sometimes don't like this kind of pattern because `cleanup_a` appears twice — so don't go crazy with it. But I really think that `result = 2; goto a_cleanup;` (and introducing that label) is not better than `cleanup_a(a); return 2;`. Only at three or four steps of resource acquisition does that really save any effort, and that's a code smell anyway. (And, of course, in C++ you get all the nice RAII idioms instead.) | | |
| ▲ | 8note 2 days ago | parent | next [-] | | > if (!b) { cleanup_a(a); return -2; } this rings alarm bells for me reading that a cleanup_c(c) has maybe been forgotten somewhere, since the happy and unhappy paths clean up different amounts of things. i imagine your python code escapes the giant tree by using exceptions though? that skips it by renaming and restructuring the goto, rather than leaving out the ability to jump to a common error handling spot | | |
| ▲ | zahlman 2 days ago | parent [-] | | > this rings alarm bells for me reading that a cleanup_c(c) has maybe been forgotten somewhere, since the happy and unhappy paths clean up different amounts of things. The exact point of taking the main work to a separate function is so that you can see all the paths right there. Of course there is no `c` to worry about; the wrapper is so short that it doesn't have room for that to have happened. The Python code doesn't have to deal with stuff like this because it has higher-level constructs like context managers, and because there's garbage collection. def get_resources_and_do(action):
with get_a() as a, get_b() as b:
action(a, b)
|
| |
| ▲ | bluecalm 3 days ago | parent | prev [-] | | You're assuming function calls or other constructs are more readable and better programming. I don't agree. Having a clear clean-up or common return block is a good readable pattern that puts all the logic right there in one place. Jumping out of the loop with a goto is also more readable than what Python has to offer. Refactoring things into functions just because you need to control the flow of the program is an anti pattern. Those functions add indirection and might never be reused. Why would you do that even if it was free performance wise? This is why new low level languages offer alternatives to goto (defer, labelled break/continue, labelled switch/case) that cover most of the use cases. Imo it's debatable if those are better and more readable than goto. Defer might be. Labelled break probably isn't although it doesn't matter that much. Python meanwhile offers you adding more indirection, exceptions (wtf?) or flags (inefficient unrolling and additional noise instead of just goto ITEM_FOUND or something). |
| |
| ▲ | Scarblac 3 days ago | parent | prev | next [-] | | A colleague recently added a linter rule against nested ternary statements. OK, I can see how those can be confusing, and there's probably a reason why that rule is an option. Then replaced a pretty simple one with an anonymous immediately invoked function that contained a switch statement with a return for each case. Um, can I have a linter rule against that? | | |
| ▲ | zahlman 3 days ago | parent [-] | | I guess "anonymous IIFE" is the part that bothers you. If someone is nesting ternary expressions in order to distinguish three or more cases, I think the switch is generally going to be clearer. Writing `foo = ...` in each case, while it might seem redundant, is not really any worse than writing `return ...` in each case, sure. But I might very well use an explicit, separately written function if there's something obvious to call it. Just for the separation of concerns: working through the cases vs. doing something with the result of the case logic. | | |
| ▲ | Scarblac 3 days ago | parent [-] | | It just looked way more complex (and it's easy to miss the () at the end of the whole expression that makes it II). And the point of the rule was to make code more readable. Basically it's a shame that Typescript doesn't have a switch-style construct that is an expression. And that nowadays you can't make nested ternaries look obvious with formatting because automated formatters (that are great) undo it. |
|
| |
| ▲ | 3 days ago | parent | prev [-] | | [deleted] |
| |
| ▲ | hamdingers 4 days ago | parent | prev | next [-] | | > and the other side just pointed at the book One of the most infuriating categories of engineers to work with is the one who's always citing books in code review. It's effectively effort amplification as a defense mechanism, now instead of having a discussion with you I have to go read a book first. No thanks. I do not give a shit that this practice is in a book written by some well respected whoever, if you can't explain why you think it applies here then I'm not going to approve your PR. | | |
| ▲ | rasmus-kirk 3 days ago | parent [-] | | Yeah, and any of these philosophies are always terrible when you take them to their limit. The ideas are always good in principle and built on a nugget of truth, it's when people take it as gospel I have a problem. If they just read the book and drew inspiration for alternative, possibly better, coding styles and could argue their case that's unequivocally good. |
| |
| ▲ | __s 4 days ago | parent | prev [-] | | Cult think loves a tome |
|
|
| ▲ | zimpenfish 4 days ago | parent | prev | next [-] |
| > I feel like no one serious uses the uncle Bob style of programming anymore (where each line is extracted into its own method) Alas, there's a lot of Go people who enjoy that kind of thing (flashback to when I was looking at an interface calling an interface calling an interface calling an interface through 8 files ... which ended up in basically "set this cipher key" and y'know, it could just have been at the top.) |
| |
| ▲ | mannykannot 4 days ago | parent [-] | | Hardcore proponents of this style often incant 'DRY' and talk about reuse, but in most cases, this reuse seems to be much more made available in principle than found useful in practice. | | |
| ▲ | zimpenfish 3 days ago | parent [-] | | There's also the "it makes testing easier because you can just swap in another interface and you don't need mocks" argument - sure but half of the stuff I find like this doesn't even have tests and you still tend to need mocks for a whole bunch of other cases anyway. |
|
|
|
| ▲ | falcor84 4 days ago | parent | prev | next [-] |
| I wonder how hard it would be to build an IDE "lens" extension that would automatically show you a recursively inlined version of the function you're hovering over when feasible and e.g. shorter than 20 lines. |
|
| ▲ | jffhn 3 days ago | parent | prev | next [-] |
| >where each line is extracted into its own method As John Carmack said: "if a lot of operations are supposed to happen in a sequential fashion, their code should follow sequentially" (https://cbarrete.com/carmack.html). A single method with a few lines is easy to read, like the processor reading a single cache line, while having to jump around between methods is distracting and slow, like the processor having to read various RAM locations. Depending on the language you can also have very good reasons to have many lines, for example in Java a method can't return multiple primitive values, so if you want to stick to primitives for performances you inline it and use curly braces to limit the scope of its internals. |
|
| ▲ | TZubiri 3 days ago | parent | prev | next [-] |
| It's an extremism to get a strong reaction, but the takeaway is that you should aim when possible to make the code understandable without comments, and that a good programmer can make code more understandable than a newbie with comments. But of course understandeable code with comments simply has much more bandwidth of expression so it will get the best of both worlds. I see writing commentless code like practicing playing piano only with your left hand, it's a showoff and you can get fascinatingly close to the original piece (See Godowsky's Chopin adaptations for the left hand), but of course when you are done showing off, you will play with both hands. |
|
| ▲ | ekjhgkejhgk 4 days ago | parent | prev | next [-] |
| Great, that's exactly how I feel with any style that demands "each class in its own file" or "each function in its own file" or whatever. I'd rather have everything I need in front of my eyes as much as possible, rather than have it all over the place just to conform with an arbitrary requirement. I said this at a company I worked and got made fun of because "it's so much more organized". My take away is that the average person has zero ability to think critically. |
| |
| ▲ | frozenlettuce 3 days ago | parent [-] | | If those demands made any sense they would be enforced by the languages themselves. It's mostly a way of claiming to be productive by renaming constants and moving code around. |
|
|
| ▲ | falcor84 4 days ago | parent | prev | next [-] |
| I wonder how hard it would be to have an IDE extension that would automatically show you a recursively inlined version of the function you're hovering over when feasible and e.g. shorter than 20 lines. |
|
| ▲ | zahlman 3 days ago | parent | prev | next [-] |
| I can assure you that I am very serious and I do cut things up almost as finely as Uncle Bob suggests. Where others balk at the suggestion that a function or method should never expand past 20 or so lines, I struggle to imagine a way I could ever justify having something that long in my own code. But I definitely don't go about it the same way. Mr. Martin honestly just doesn't seem very good at implementing his ideas and getting the benefits that he anticipates from them. I think the core of this is that he doesn't appreciate how complex it is to create a class, at all, in the first place. (Especially when it doesn't model anything coherent or intuitive. But as Jeffries' Sudoku experience shows, also when it mistakenly models an object from the problem domain that is not especially relevant to the solution domain.) The bit about parameters is also nonsense; pulling state from an implicit this-object is clearly worse than having it explicitly passed in, and is only pretending to have reduced dependencies. Similarly, in terms of cleanliness, mutating the this-object's state is worse than mutating a parameter, which of course is worse than returning a value. It's the sort of thing that you do as a concession to optimization, in languages (like, not Haskell family) where you pay a steep cost for repeatedly creating similar objects that have a lot of state information in common but can't actually share it. As for single-line functions, I've found that usually it's better to inline them on a separate line, and name the result. The name for that value is about as... valuable as a function name would be. But there are always exceptions, I feel. |
|
| ▲ | lucketone 4 days ago | parent | prev | next [-] |
| It was written in different times, different audiences. (When variable names t,p,lu were the norm) It was useful for me and many others, though I never took such (any?) advice literally (even if the author meant it) Based on other books, discussions, advice and experience, I choose to remember (tell colleagues) it as “long(e.g. multipage) functions are bad”. I assume CS graduates know better now, because it became common knowledge in the field. |
|
| ▲ | ngruhn 4 days ago | parent | prev | next [-] |
| I know plenty of Java/C# developers who still suffer from this mind virus ;P |
|
| ▲ | Rapzid 3 days ago | parent | prev | next [-] |
| The Ruby ecosystem was particularly bad about "DRY"(vs WET) and indirection back in the day. Things were pretty dire until Sandi Metz introduced Ruby developers to the rest of the programming world with "Practical Object-Oriented Design". I think that helped start a movement away from "clever", "artisanal", and "elegant" and towards more practicality that favors the future programmer. Does anyone remember debugging Ruby code where lines in stack traces don't exist because the code was dynamically generated at run time to reduce boilerplate? Pepperidge Farm remembers. |
|
| ▲ | xlii 4 days ago | parent | prev | next [-] |
| Haskell enters the chat Haskell (and OCaml I suppose two) are outliers though as one is supposed to have a small functions for single case. It's also super easy to find them and haskell-language-server can even suggest which functions you want based on signatures you have. But in other languages I agree - it's abomination and actually hurt developers with lower working memory (e.g. neuroatypical ones). |
| |
| ▲ | danielscrubs 4 days ago | parent [-] | | It’s because maths are the ultimate abstraction. It’s timeless and corner cases (almost) fully understood. Ok maybe not, but at least relative to whatever JavaScript developers are reinventing for the thousand time. |
|
|
| ▲ | embedding-shape 4 days ago | parent | prev [-] |
| > where each line is extracted into its own method Never heard of "that style of programming" before, and I certainly know that Uncle Bob never adviced people to break down their programs so each line has it's own method/function. Are you perhaps mixing this with someone else? |
| |
| ▲ | eterm 4 days ago | parent | next [-] | | This is from page 37 of Clean Code: > Even a switch statement with only two cases is larger than I'd like a single block or function to be.
His advice that follows, to leverage polymorphism to avoid switch statements isn't bad per-se, but his reasoning, that 6 lines is too long, was a reflection of his desire to get every function as short as possible.In his own words, ( page 34 ): > [functions] should be small. They should be smaller than that. That is not an assertion I can justify. He then advocates for functions to be 2-3 lines each. | | |
| ▲ | embedding-shape 4 days ago | parent | next [-] | | > to leverage polymorphism to avoid switch statements [...] was a reflection of his desire to get every function as short as possible. That's both true, but long way away from "every line should have it's own method", but I guess parent exaggerated for effect and I misunderstood them, I took it literally when I shouldn't. | | |
| ▲ | eterm 4 days ago | parent [-] | | I've edited my comment to add more context to that quote. He absolutely advocated for the most minimal of function lengths, beyond what is reasonable. |
| |
| ▲ | zephen 3 days ago | parent | prev [-] | | He has expressed admiration for lisp, and he comes from a time before IDEs. These may color his desired level of complexity. |
| |
| ▲ | troupo 4 days ago | parent | prev [-] | | > I certainly know that Uncle Bob never adviced people to break down their programs so each line has it's own method/function There's a literal link to a literal Uncle Bob post by the literal Uncle Bob from which the code has been taken verbatim. |
|