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.
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.
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?
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.
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.)
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).
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
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)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.