25

I stumble across this case somewhat often, and I'm surprised about finding so few similar discussions around the web. This question is very related, but my problem is that I want a method that does the more general "do X if Y" rather than "do X if needed". The answer in that link is to use the prefix Ensure, but that word does not fit if ensuring X is not the method's intention.

The scenario I have in mind is this:

void mayPerformAction() {
    // Do some preparatory calculations
    // ...
    if (shouldPerform) {
        // Perform action
        // ...
    }
}

The reason I am not using two separate methods (shouldPerformAction() and performAction()) is because both the condition and the action depend on some preparatory calculations, which would have to be repeated otherwise. Now, my question is: what is the most logical and readable name for the method mayPerformAction()?

To clarify, it is important to the caller that the action may sometimes not be executed, otherwise it seems logical to me to use performAction().

I admit that this is kind of an XY-problem, and there are multiple solutions posted, each of which have good arguments for and against them. To summarize:

  • Abstract away the doubt and give it a less detailed name, e.g. just performAction().
  • Prefer clarity and do the calculations twice; the performance difference will be negligible in many cases anyway: if (shouldPerform()) performAction().
  • Same as above, but store the shared result of the calculations in a global variable or return it (I prefer the latter) so no resources are wasted.

I feel like the best approach depends on how 'serious' the condition is and how expensive the preparatory calculations are; as such I'm leaving the question unanswered for now.

Robert Harvey
  • 198,589
  • 55
  • 464
  • 673
aviator
  • 359
  • 1
  • 3
  • 6
  • 2
    While I agree wholeheartedly with CandiedOrange's answer below, there is some precedent for, for example, a method that fills a buffer with needed data, but does nothing if the buffer is already filled. I prefix such methods with the word "Ensure," as in `EnsureBufferFilled()`. – Robert Harvey Feb 05 '18 at 03:28
  • 1
    @RobertHarvey I've written `ensure` methods myself. Heck I've written method names with `if` in them. But I'll take a good abstraction over either if i can find one. Sadly I can't always find one. If I can't, well, I'll take your `ensure` over `ifXthenY()` any day. :) – candied_orange Feb 05 '18 at 04:33
  • @CandiedOrange: It is, in fact, the only time I've ever put "condition words" in the name of a method that didn't return a boolean. – Robert Harvey Feb 05 '18 at 16:07
  • I've used `...IfRequired(...)` in addition to `Ensure...(...)` when there's no good alternative but the fact something won't necessarily happen needs to be explicit. – GoatInTheMachine Feb 05 '18 at 16:20
  • @GoatInTheMachine I wholeheartedly disagree. Doing something or nothing is an implementation detail. A good name hides implementation detail behind an idea that ensures that the details can change yet will come as no surprise to someone who had previously only known the name. – candied_orange Feb 05 '18 at 16:25
  • @CandiedOrange on the whole I agree (and totally agree for public interfaces), but there are exceptions, and in those (very rare) cases `...IfRequired()` is a nice convention I've seen and used. – GoatInTheMachine Feb 05 '18 at 17:27
  • Please stop using "EDIT" in your posts. This isn't a forum, and [we already know that you've edited.](https://softwareengineering.stackexchange.com/posts/365310/revisions) Instead of tacking on addendums, edit your question organically so that it reads like a complete, single, coherent question. It's also not necessary to summarize the answers in your question or provide editorial commentary about how you're going to use the checkmark; comments are better suited for that. – Robert Harvey Feb 09 '18 at 16:49

6 Answers6

14

You're trapped in a structural way of thinking. A name should abstract away implementation details. It shouldn't become a short hand for them.

 IfBirthdayBuyCake();

is a terrible name. It exposes implementation details as badly as if you wrote them here naked.

I would reach for a better name and a better abstraction.

celebrateHoliday();

Somewhere in it you'll likely find something like this

if ( birthday() ) {
    buyCake();
}

and maybe some other holidays. When you call it, it may do nothing at all. Which means when you call it you don't have to know if today is a holiday. That not-knowing frees you from that detail. That's what a good name should do.

If you'll forgive a longer name:

celebrateTodaysHoliday()

makes it a little clearer that if today has no holiday then this does nothing. This makes the scope of discussion explicit.

candied_orange
  • 102,279
  • 24
  • 197
  • 315
  • 6
    But what if `celebrateHoliday()` was called daily (figuratively)? I would be slightly confused as the name implies that every day is a holiday, and prefer rewriting to have an `if(isHoliday())` check first. Now suppose the date is hard to retrieve and both methods need the result: then we're back at my original point. – aviator Feb 04 '18 at 23:47
  • 2
    Like I said, you're trapped in structural thinking. I'd have no trouble calling it daily. That's because it's a command not an event. You're not promised that it will do anything at all. – candied_orange Feb 05 '18 at 00:00
  • 1
    @aviator whoever is reading the method name is going to do it within a context. The method is not floating in the immaterium, isolated from everything else. On the other hand, it's likely the method is going to be called when the conditions for its execution are met. The conditions are not necessarily evaluated within the method. `celebrateHolidays` has well-defined responsibility. To celebrate something (the *how to*, not the *when*). Calculate which holiday is *could be* beyond its responsibility. – Laiv Feb 05 '18 at 08:53
  • 4
    Viewing a method as a command rather than a promise is a good point. – aviator Feb 05 '18 at 09:56
  • 3
    @aviator thank you. Sometimes the postcondition is "well I tried". It goes against instinct. We want to dictate so we know what will happen. We want to have all the details. We end up drowning in them. Polymorphism and abstraction only work when you don't know the details. Learn to enjoy not knowing and not caring. – candied_orange Feb 05 '18 at 10:03
  • What about `celebratePotentialHoliday()`? – Ralf Kleberhoff Feb 05 '18 at 10:45
  • 4
    @CandiedOrange I must disagree - "celebrateHoliday()" would be a very bad name for a function that checks if there is a holiday to celebrate, and - if there is one - performs the necessary steps (e.g. buys cake). What does "celebrateHoliday()" appear to promise? That it celebrates Holidays. Period. There is no "if today is a holiday" condition in that name. – CharonX Feb 05 '18 at 12:53
  • Let me give you a counter example: A "getApples()" function that gathers apples from apple-trees - if there are no trees, it does not get apples. Note that trees are nowhere mentioned in the arguments. You call getApples() without trees. And getApples() does not get apples (but that still counts as a successfull execution of getApples() because "it tried"?). Not very intuitive. – CharonX Feb 05 '18 at 12:57
  • 3
    @CharonX well you're right about it not being intuitive.Getting nothing when you asked for something isn't what you expect even when it's correct. That's why we have a number like zero but the romans didn't. It's not an intuitive idea but it's a good idea.It's expressed many other ways: null, the null object pattern, the empty string, the maybe monad, and with methods that sometimes do nothing at all. – candied_orange Feb 05 '18 at 13:33
  • 1
    I disagree. I think the implementation details being exposed are very minimal and can actually aid the caller much more than it hurts by leaking the details. A good example would be if I saw a `startServer(port)` method being called in multiple places I would immediately think something is wrong. I would have to dig into the implementation details to see that it was actually only starting the server if it hasn't been started yet. If I saw `startServerIfNotStarted` or `maybeStartServer` I wouldn't have to dig. – pllee Feb 05 '18 at 18:11
  • Actually thinking more a bit, I really think the severity of the method matters. I am a proponent of conditional names but I rarely use them publicly unless it is a severe action that is implied should only happen once. As a consumer of `buyCake` I really don't care about the condition for the most part. As a consumer of `startServer` I do. – pllee Feb 05 '18 at 18:16
  • @pllee and now I can't redesign to start a different server each time without dealing with all the assumptions the clients made because they thought they understood exactly how this would be implemented. I reserve the right for every one of my methods to do nothing when they feel like it and not tell their clients a thing about it. – candied_orange Feb 05 '18 at 18:17
  • 1
    @CandiedOrange not sure if it wasn't implied but starting a server on the same port it not possible it was meant to be an atomic operation that only should happen once. In your example I would have a `maybeStartServer` and if I wanted to get rid of the conditional logic I would add a `startServer` method or vice version. Oh and of course you have the right to do anything, your `startServer` may not start a server at all but I doubt your clients would be happy about that. – pllee Feb 05 '18 at 18:31
  • @pllee I can start multiple servers on multiple IPs on multiple equipment all with the same port. I can also start none at all because we're not even using servers any more. Stop letting clients dictate implementation. It's none of their business. – candied_orange Feb 05 '18 at 18:38
  • @CandiedOrange making methods unpredictable can lead to subtle bugs; at the very least `celebrateHoliday` could return whether or not something was celebrated. All too often I see methods that do "nothing" because somebody passed in a bad parameter (like a null pointer). Instead of clearly rejecting this, they just add logic like "if null then return" potentially hiding a bug. – john16384 Feb 06 '18 at 13:19
  • @john16384 no it's not going to return news of what happened because that will force the client to deal with what happened. Instead establish a contract where the client doesn't care what happens. If it works or not, the result, are all things that other, non client code, will deal with. The clients only concern is when to issue this command. What happens after that is the business of other code. You call this unpredictable. I call it polymorphism. The client shouldn't have to predict what happens. – candied_orange Feb 06 '18 at 13:55
  • 2
    @CandiedOrange I think you're confusing the part where clients don't care *how* something happens with *whether* it happens. It is very important for clients to know that something happened or nothing at all. It is so important in fact that in the "nothing" case, most methods will throw an exception, alerting the client that their logic might be in error. You don't want your client wondering "Why wasn't there any cake bought? I called it to celebrate the holiday, didn't I? I even checked it is Christmas beforehand.." ..and then it turns out it doesn't celebrate christian holidays... – john16384 Feb 06 '18 at 14:19
  • @john16384 I'm not confusing 'whether' with 'how'. I'm explicitly saying that 'nothing' is one of many possible 'how's'. Doing nothing does not mean you must throw an exception. Exceptions are for exceptional cases. Doing nothing might be the most typical result. – candied_orange Feb 06 '18 at 14:26
  • @candied_orange, I like your answer, a lot. It helped me name my methods better. But I'm running into an issue by using this convention. If we're abstracting away implementation details, you lose details over how the method works. You argue that this is a good thing. – Krzysztof Czelusniak May 16 '19 at 16:42
  • @candied_orange However, imagine that I need an instance of a `Person`. According to your logic, we shouldn't care how the method gets a `Person`, just that it does. We shouldn't care if it's getting a `Person` from a database, building a `Person` object, or simply returning a property. Would you always name this method `getPerson`? Or would you be more descriptive and name the method `loadPerson`, `fetchPerson`, or `buildPerson`? – Krzysztof Czelusniak May 16 '19 at 16:43
  • @candied_orange By following your answer, I feel like I'd prefix every method that returns a value with `get`, because we don't care how that method returns a value, just that it does. I don't have a problem with this, but I feel like other engineers will think that this is a bad practice. Just by reading other posts, it seems like this would be a bad practice. (https://softwareengineering.stackexchange.com/a/198170/320983) (https://softwareengineering.stackexchange.com/questions/182113) – Krzysztof Czelusniak May 16 '19 at 16:47
  • 2
    @KrzysztofCzelusniak I think of the `get` wart from the java bean convention as more of a sad result of insisting that all class names be nouns and all method names be verbs. I don't use it outside of DTO's that are not truly object oriented anyway. I don't name factories `getSomething()`. I'd go with `something()`. It may be called `Something.builder()`. It may be called `Something.withX(1).withY(2).build()`. But these differences focus on how they're used. Not on how they work. How they work is private. For example, you don't know if these always build new things or return cached old things. – candied_orange May 16 '19 at 17:42
3

I would advise against putting conditionals in your method names. If you want the calling code to read like a conditional, use a conditional:

if (cakeIsNeeded) buyCake();

Or, if ternary operators or short-circuits are your thing:

cake = cake == null ? buyCake() : cake;
cake = cake || buyCake();

Otherwise, you can either silently ignore repeat calls, use memoization, or throw exceptions in the method to deal with repeat calls — whatever feels most appropriate and least surprising for the particular method.

If you have a method name that feels like repeat calls would re-perform their action(s) but won't, one way to beat the "surprise" is to add an optional force, skipCache, or similar boolean parameter. (The name of the flag should be relevant to the method name and/or skip-logic.)

All that said, I'd tend to look for a verb that implies what the caller needs, rather than what the method does. In the example of cake, the caller wants the cake and doesn't care much where it comes from. It sounds to me like you just want to getCake() or findCake().

Both of those names communicate that Cake will be returned. They don't reveal to the caller how that Cake will be located. It could be purchased, taken from the counter, or made by magical elves. Those are implementation details.


One important caveat to all of this: These naming patterns tend to be highly idiomized. Refer to your language's internal libraries for examples of how they handle this. And talk to your team to decide on your own internal idioms.

svidgen
  • 13,414
  • 2
  • 34
  • 60
2

Reasonably common is "performActionIfNeeded" (a method that will cleverly figure out whether an action is needed and only perform it in that case) vs. "performActionIfWanted" (a method that will cleverly figure out that you want to perform an action and only perform it in that case).

gnasher729
  • 42,090
  • 4
  • 59
  • 119
  • 2
    I've never seen this variation. – Robert Harvey Feb 05 '18 at 03:32
  • 3
    @RobertHarvey From the mighty Spring Framework (Java) **`org.springframework.data.redis.cache.RedisCache#putIfAbsent`** I Knew I had seen this somewhere. – Laiv Feb 05 '18 at 08:10
  • 1
    @Laviv Java 8's Map interface has `putIfAbsent` as well. – pllee Feb 05 '18 at 19:23
  • `putIfAbsent` is, IMO, a separate and very specific case, required by "threading stuff". Not very related to OP's question, so not a good example. – user949300 Feb 06 '18 at 00:43
  • 1
    @user949300 `putIfAbsent` in Java 8's Map has nothing to do with threading. Also if it did I fail to see how that matters for the question. – pllee Feb 06 '18 at 17:21
  • These forms do exist and have their place. But should always be understood as a failure to abstract implementation details. That's why they are predominantly found in libraries and frameworks that have no idea what your domain is or your ubiquitous language. That's why they couldn't find a good name. Please don't imitate them unless you're stuck doing the same thing. – candied_orange Feb 07 '18 at 06:56
  • 2
    @RobertHarvey A few examples can be seen in Apple's `UIKit` framework, e.g. [layoutIfNeeded()](https://developer.apple.com/documentation/uikit/uiview/1622507-layoutifneeded), [updateConstraintsIfNeeded()](https://developer.apple.com/documentation/uikit/uiview/1622595-updateconstraintsifneeded) and [loadViewIfNeeded()](https://developer.apple.com/documentation/uikit/uiviewcontroller/1621446-loadviewifneeded). – Wolfgang Schreurs Feb 09 '18 at 08:31
2

If there is a better alternative that still prevents double code execution, feel free to let me know :)

If preparatory calculations is really expensive and calculating it twice would really hurt the performance (so it is not a case of premature optimisation) I would still seperate condition and action into two methods because reading

if (shouldPerform()) doPerformAction()

is much more intituitive. To cope with the state calculation you can enclose the preparatory calculations into class state like this:

public class MyClass {
    private CalculationResult preparatoryCalculationResult = null;

    public boolean shouldPerform() {
        initIfNeccessary();
        return preparatoryCalculationResult.shouldPerform();
    }

    public void doPerformAction() {
        initIfNeccessary();
        preparatoryCalculationResult.doPerformAction();
    }

    private void initIfNeccessary() {
        if (preparatoryCalculationResult == null) {
            preparatoryCalculationResult = 42; // very expensive calculation ;-)
        }
    }

}
k3b
  • 7,488
  • 1
  • 18
  • 31
0

Going on:

To clarify, it is important to the caller that the action may sometimes not be executed, otherwise it seems logical to me to use performAction().

It seems that the caller has some knowledge of what is going to happen. I think it is therefore warranted that the caller can decide whether or not the action should be performed.

An example:

A function should delete all files that match a pattern, but only if their total size exceeds some value. The scanning is expensive, but must be done in order to check the condition. The delete action requires knowledge of the scan in order to prevent scanning twice.

I would simply split this in two methods:

// Returns a ScanResult containing files matching a pattern
ScanResult performScan(Pattern pat);  

// Deletes a list of files
void delete(List<File> files);

The condition can now easily be externalized:

ScanResult result = obj.performScan(...)

if(result.totalSize() > 100 * 1024) {
    obj.delete(result.getFiles());
}
john16384
  • 155
  • 5
0

Part of the problem is the void return type of the original method. It would be better to return some kind of result struct object to the caller to indicate differing degrees of success. This gives the caller the information about what did or didn't happen, in case it needs to know to pass the message back to the UI or something similiar. It also quickly/clearly signals to the next coder to touch the code that the method called might not perform heavy operations with every call.

public class OperationResult
{
    public OperationState State { get; set; }
    public string OperationMessage { get; set; } // result codes, etc
    public enum OperationState { NoOperationPeformed, OperationSuccessful } // can have more if needed
}

You might return the whole class above, if you need the OperationMessage (or other data about the call), or just the enum OperationState if you just want to signal that the 'real' work of the function was skipped.

Its possible a fellow coder might assume from that void return that the method always does the same thing. Returning the above could help indicate through IDE intellisense that there's some other possibilities.

Graham
  • 3,833
  • 1
  • 21
  • 18