Refactoring a 300-line if
groupserver.org
groupserver.org
I would have left it as the big if.
Source: https://github.com/groupserver/gs.group.member.canpost
1. https://github.com/groupserver/gs.group.member.canpost/blob/...
Well, an if/elif/elif block that spans 300 lines is often much more difficult to comprehend than 100 - ten line functions (especially if all 100 share the same function signature).
That said, I'm not sure if I prefer the xml configuration option. But the design is overall likely more flexible now than it was before.
In this concrete case, these many functions are now spread over the entire codebase (spanning many repos). In many classes and inheritance trees and driven by a ton of configuration so there are now many many more possibilities. Here is one implementation https://github.com/groupserver/gs.group.type.discussion/blob... which i don't find very readable personally
Reading the README in the first repo I can now see the flexibility and coupling the rules more closely to their objects was a design goal. So in fairness the new code is doing a lot more then (what i imagine to be) old code to be doing. Also I now note this code is 3+ years old so the zope dependency (which i know nothing about) makes some more sense.
Guess it happens.
[1] https://github.com/groupserver/gs.group.member.canpost/blob/...
What you want to do is make sure you don't need another 300 lines to implement that problem, or that if you do, that code is easy to read and to maintain.
Figure out what the trust modality in your application is, and normally it's a simple graph or can be managed with a straight-forward application of world/group/user UNIX-style permissions.
Also, singleton (yuck) accessed via a helper function: yuck. Just import it.
This starts to make sense where OP professes fondness of XML: we have a Java developer!
That said, I don't understand yet why its code should be split into 9 pages of separate repositories (see https://github.com/groupserver/)...
But anyway, I see the world of enterprise Java has reached Python.
By that, you mean code that is fast, easy to maintain, typed, tested and scalable?
IE: Using XMLs for things that they are not needed for.
The idea is sound since that's exactly what rule engines are for but I feel that if you don't also post the final listing of the rules you end up with, you haven't really made the case that you improved the code.
if(self.predicate_function())
foo()
else
bar()
and self.predicate_function() is only called once in the program (this is the case several times in the slideshow example), try pulling the if statement into the function (and simplify), making a self.handle_case_something()
function instead. If the predicate is called more than once, consider subclassing. You're already doing OO, may as well do it right.send("#{group.type}_notification") or its python equivalent.
Maybe a hash table with strings as keys and functions as values for a less 'scripty' language.
Perhaps I'm thinking the variable group is of type group, but it also looks like it suffers from poor separation of concerns... eg. Why are groups sending email?
Probably also a good candidate for guards instead of nested ifs
Replace
if(A) {
// 300 lines of code...
}
else {
return FALSE;
}
with if(!A) {
return FALSE;
}
// 300 lines of code... for rule in self.rules:
if not rule.canPost:
return False
return True
That's the same number of lines of code, doesn't require two imports, and is way easier to follow. I love functional programming but Python (intentionally, see reduce in py3) is not a language for doing it. return all(rule.canPost for rule in self.rules) /* I don't know why, but if you remove this everything breaks */
I wonder if that is still there, it's been over ten years.Java and PHP allow cases that have logic and no break/return, and it's (strongly) arguable that it's a bug. I think C# doesn't allow that. It allows fall-through, but only if the case is empty.
And because I absolutely love this article, somewhat related: http://www.stilldrinking.org/programming-sucks
eg. Why is this method concerned with whether the notification is email or something else...
Are persons not also the case of a group with one member?
It seems less like refactoring and more like hiding complexity in frameworks.
all([postAllowFilter() for postAllowFilter in filters]) Then just take a list of all of your functions.
Really, there just should have been a restructuring of the initial code flow. It didn't look too messy.
Maybe because I stopped using variable names like "retval", "data" or even "result" - because they convey so little meaning.
How can anyone write a sin function like:
result = sin(degrees*PI/180);
return result
when you OBVIOUSLY should be writing: sin_of_degrees_times_pi_divided_by_180 = sin(number_of_degrees_in_degrees*PI/180)
return sin_of_degrees_times_pi_divided_by_180
One is unreadable garbage. (Result? WTF does that mean)? While the other is clean, maintainabe code. Anyone can tell at a glance what sin_of_degrees_times_pi_divided_by_180 holds. But result? In a function that calculates sin values? You might as well label your variables OX00001, OX000002 and so forth. return sin(degrees*PI/180);I have zero qualms about writing two lines
bool passes = /*stuff goes here*/
if (passes) {
}
whereas it is trivial that you could fold this into one line. the ONLY thing you're adding is the term "passes". Which most certainly is meaningful to the human reading it.Compare:
if (!(i%2))
with bool isodd = i%2;
if (!isodd)
which test do you think you'll accidentally flip the parity of?Likewise I find "retval", "data", etc, to be perfectly fine variable placeholders while you treat the thing for a few lines.
It will also allow you to put a breakpoint on the return statement and have it print the value out or for it to be used in other debugging.
This is especially useful when the function call is used within a more complicated formula.