0

Here are a couple of examples in Python:

clearly_even = 2 * get_integer()
print(solve_for_any_integer(clearly_even))

def solve_for_any_integer(x):
  while x % 2 == 1:
    x = make_even_from_odd(x)
  return ... # Solve for even

When it's clear that an even number is passed, I'd make a solve_for_even function and call it.

for car in list_cars():
  if car.driver is None:
    continue
  if not eligible_to_drive_taxi(driver):
    continue
  print(driver.name)

def eligible_to_drive_taxi(person):
  if person.is_drunk:
    return False
  if person.driver_licence is None:
    return False
  if person.driver_licence.years_of_experience < 3:
    return False
  return True

Assuming that drivers are sober and have a driver's license, why not making a special function or method that checks ​a license only?

I see several benefits of using more specific code:

  1. Less code to follow through when reading.
  2. Methods have less diverse use cases and, therefore, became simpler.
  3. Using more specific methods gives more insight into assumptions made by the author. Fewer "why" questions and WTF moments.

To me, it's always been obvious that using a more specific method is the only way to go. Admittedly, it may be controversial, but what are other reasons to stick with either approach?

Although, I see many developers use more generic methods so often so there should be a name and some articles on this phenomenon. Are there any?

2
  • 1
    I don't think what you're saying is very controversial (except maybe at first glance) - you're basically saying to break the problem down into more self-contained subproblems, and give good names to these concepts, in the spirit of the single responsibility principle and all that - it's just a question of how granular one wants to be with that, and that's where people have different views. Jul 7, 2021 at 22:13
  • 1
    And it also depends on the problem domain itself; e.g. solve_for_any_integer may be more appropriate if there's no special significance to even integers, even if you know that in that particular use case the integer is even. Jul 7, 2021 at 22:13

2 Answers 2

3

The straw that breaks the camel's back

Very simply put, the extra effort you're talking about is a straw. The camel does not care about dealing with the added straw, so that's all fine. But by reapplying this "another straw makes no difference", eventually the camel's back will break. And it's not the final straw's fault. It's the fault of all of the unnecessary straws you put on.

why not making a special function or method

There's a progression of events here.

  • Let's make a special function for a particular case.
  • Now we have two functions.
  • Wait, we're violating DRY. We have to create abstractions to reuse our logic.
  • Now we have three functions, the abstract one and the two concrete variants.
  • Should we make more special functions for particular edge cases? Because we did one, but there's three more. Why would this edge case be treated differently? Bob thinks the other edge case is more important, anyway.
  • Now we have many functions to cover all permutations of which edge cases apply in which scenario.
  • Now we have to scroll through a ton of code to find the bit we're interested in. Sure, the bit itself is readable, but it's lost in a jungle of individually readable bits.
  • We've undone the readability we tried to achieve.

You may think the "many edge cases" argument is only applicable to rare circumstances, but you already hit on that point with your taxi driver example. You singled out "if not drunk and has a license" as a precondition. So what about using "if not drunk and having 3+ years of experience" as a precondition? What if "having a license" is a precondition by itself?

Your arguments

You've made it hard for me. Often, I find myself having to explain that keeping code short and clean is something that people often gloss over and don't understand how much it helps them. You've gone the other way, and now I find myself arguing the point in the opposite direction.

To me, it's always been obvious that using a more specific method is the only way to go.

A very big red flag for me is "the only way". There is more than one way to skin a cat. There is merit to your intention but it is not an absolute, nor is it the epitome of perfection.

I appreciate the intention of your approach. You are making an effort with the intention to enhance something. But overzealousness looms, and you need to be aware of both the lower and upper boundaries of reasonability. This latter part is where I think you've gone off the rails a bit.

  1. Less code to follow through when reading.

Yes, each individual method becomes more readable. However, the gains from simpler methods are counterbalanced by the much larger amount of methods to know about, be able to use, and support in the future. And that balance needs to be struck very, very carefully.

  1. Using more specific methods gives more insight into assumptions made by the author. Fewer "why" questions and WTF moments.

Similarly to the point above, you are trading away WTF moments for readers, and instead gaining WTF moments for writers who now have to decide between many different options, which entails learning about all of them, their differences, and having to evaluate which variant is best for them in this particular use case.

Enough WTF moments for writers leads to writers making mistakes or simply getting exhausted from the effort, which in turn will negatively impact the quality of their work, which in turn negatively impacts the reader experience.
From my experience as a consultant in many companies that got stuck on a project, the worst codebases that became unmaintainable messes were not due to lack of effort, but rather an overabundance of unchecked misguided effort that overcomplicated a simple thing and lost track of the ultimate goal.

  1. Methods have less diverse use cases and, therefore, became simpler.

Think of it like being a mechanic. Yes, a mechanic has several tools in their workshop, because having a single tool for everything tends to lead to an inferior tool overall (jack of all trades, master of none). However, that doesn't mean that there isn't an upper limit on tool count. You have to also consider having too many tools is also a problem for the mechanic.
Taken to a silly level, eventually the mechanic will have to have an entire warehouse and staff just to find the right tool among his collection of millions.

I'm of course exaggerating for the sake of example, but this is the same balance you need to strike. Your argument seems very much rooted in the "more is better" ideology, and I don't quite detect a "but too much is bad" modifier in your argumentation.

A better approach

To be clear, subdividing your logic is good (but never forget that, as with all things, there is an upper boundary). The red flag I'm responding to is the arbitrary preconditions. If you were to simply abstract the validation rules, that's perfectly fine, e.g.:

return ValidateSober(driver)
    && HasLicense(car, driver)
    && HasMinimumLicenseExperience(driver, 3);

By abstracting the validation rule into separate methods, you are able to separately define the concrete validation evaluation, while retaining the readability in this orchestrated method.

Note that I used submethods here for abstraction. That's not the only solution. E.g. you could use validation rule objects, and you could even make these accessible via a base type so that you can dynamically compose the specific validation rules to be followed. How complex you make this abstraction depends on how much benefit it gets you from doing so.


Conclusion

I can't give you a concrete right answer, because the right answer depends on context. Depending on factors such as future extensibility, variability of configuration (e.g. different years of experience required for different licences), scope of the application, size of the development team, ... the choices for what level of abstraction is warranted change.

But I would advise against justifying your approach using arbitrary preconditions. For example, I have no idea why "not drunk and having a license" is grouped together as a precondition, especially if you have two license-related validation rules and yet only include one of them as a precondition.

This suggests to me that your approach is arbitrary, and "arbitrary" tends not to align with "conventionally agreed", which is the basis for readability.

2
  • I didn't expect such a detailed answer. Thank you! What I did wrong when composing the question is, indeed, not thinking of the other extreme. Surely, many new similar methods for each case would become an even bigger complication. And they'd make the interface more coupled to client code. I was assuming otherwise equal conditions, i.e., that number of methods and the interface complexity doesn't change significantly, that everything is just rearranged. Jul 8, 2021 at 15:18
  • The preconditions in the second example are quite sloppy. It took me half an hour to think up, and I didn't manage to make them clearer... But I'm really grateful that you steered the discussion to this. Jul 8, 2021 at 15:18
2

There's less code to read through when you read the utility method. But there's more effort when reading through the callers of the utility method; the reader now has to understand why there are several versions of check_eligibility() and which one to call under what circumstances.

In general, it is a bad idea to offer multiple versions of a function that differ only in small ways, e.g. in the amount of checking they do; naming such variants well is really hard, and the reader has a much harder time keeping them mentally apart. You also introduce the possibility that people use the wrong variant, leading to defects caused by problems that you've already handled, but in another place!

I occasionally encounter APIs organized along such over-specific lines, and almost always I mentally curse the author because they've traded complexity and hence bug-proneness for a negligible amount of efficiency.

1
  • Thank you for the answer. I didn't really mean to create many new methods, but rearrange code, extract and inline something to make methods more cohesive in terms of purposes these methods are used for. Jul 8, 2021 at 15:30

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge that you have read and understand our privacy policy and code of conduct.

Not the answer you're looking for? Browse other questions tagged or ask your own question.