5

Is there a commonly (or rarely) used pattern for a "confirming you want to do this" message when calling a function from a library?

I have a API that exposes some operations that are potentially dangerous if misused (would cause unwanted changes to the database). I would like to pass a warning back to the caller to double check that this is on purpose. This is both to guard against other programmers in the organization misusing the API and to prevent an end-user from accidentally executing this command from a form without thinking about it.

There are really three use cases for this API function:

  1. Execute it against legit data. Should succeed with no warnings.
  2. Execute it against bad data. Should fail with an exception.
  3. Execute it against questionable data. The validate logic sees that it is a non-standard case, but maybe the end user knows what they are doing and is doing this on purpose.

I have been trying to think of a way to put case 3 into my API.

One way I have thought of revolves around a "confirmation token"

Sub DoRiskyOperation(thisData As String, Optional confirmationToken As ConfirmationToken)

    If Not Validate(thisData) Then
        Throw New ArgumentOutOfRangeException("thisData", $"Cannot perform this operation on {thisData}.")
    End If

    If Not confirmationToken.ItsOK Then
        If Not TotallySafe(thisData) Then
           Throw New Warning("thisData", $"Are you sure you want to do this?", _
                             New ConfirmationToken(AreYouSure))
        End If
    End If

    'perform risky operation

End Sub

Another way I was considering is more of an "ask first" which is sometimes frowned upon:

Enum PredictedSuccess
    WillSucceed
    WillFail
    WillSucceedButIsRisky
End Enum

Private LastDataAskedAbout As String

Function IsOperationRisky(thisData As String) As PredictedSuccess

    LastDataAskedAbout = thisData
    If Not Validate(thisData) Then Return PredictedSuccess.WillFail
    If TotallySafe(thisData) Then
        Return PredictedSuccess.WillSucceed
    Else
        Return PredictedSuccess.WillSucceedButIsRisky
    End If
End Function

Sub DoRiskyOperation(thisData As String)

    If Not Validate(thisData) Then Throw New ArgumentOutOfRangeException("thisData", $"Cannot perform this operation on {thisData}.")

    If Not TotallySafe(thisData) AndAlso LastDataAskedAbout <> thisData Then
        Throw New InvalidOperationException($"Hey, you were supposed to check before you try doing something this dangerous to {thisData}!")
    End If

    'perform risky operation

End Sub

Is there another way to encode the concept of this "confirmation handshake" into an API that I am not thinking of?

In case anyone is interested, the specific use - case is updating standard cost while there is inventory on hand. Accountants are picky about these kind of things.

  • Maybe you could require the caller to pass in a fixed token like "&dangerous=1" (if it's a web API), purely as a reminder to the API client that they should ask the user for confirmation. But you can't force them to ask the user for confirmation. – immibis Jan 2 '17 at 0:46
15

You're trying to solve a human factor problem with an API-level solution, and I believe it's not going to work. It is most likely the case of XY problem.

If the "risk" is losing the data because someone did something stupid (or malicious), you should eliminate this possibility (of data loss) altogether. There should be no "risky" methods whatsoever, because human mistakes are not something you can eliminate.


The correct ways to do that may be:

  1. Fool-proofing: Make it so that backup is created, or the data is only being marked as deleted, and not actually deleted, etc.
  2. Dangerous UI function (as opposed to API change): Add a confirmation dialog to the UI, with huge, bold capital letters saying "YOU'RE ABOUT TO DELETE ALL THE DATA". Also, make sure you log this operation in a proper way, so you always can find the angry employee who deleted your database then they got fired.
  3. Access-control: make a risky operation only available to people who are responsible for possible damage. Make sure they understand the risk. Don't give access to anyone else.
  • There is wisdom in this. +1 – Mike Jun 9 '16 at 17:33
  • I don't like the term risky. I would prefer non-reversible or permanent. If it's something time consuming like "delete everything" that might get interrupted before it's done, I would also say that it wasn't atomic. – Martin Soles Jun 9 '16 at 17:42
5

I use your first approach but without throwing anything. The subroutine takes one extra argument, like yours - "I know this is risky but do it anyway", and it returns three possible values:

  • Success
  • Failure
  • Blocked because it's risky and the caller didn't say "do it anyway".

Apart from your use case, this is also useful for the case where the operation is something that may or may not require authorisation (such as a password). In that case the extra argument is the authorisation token, and the third return value is "Blocked because the token is missing or invalid". This adapts very nicely to the case where the calling program sometimes remembers authorisation tokens for reuse.

  • I had considered this approach (on the model of DeleteFile (True)), but I think it is a weaker guarantee than I am going for in that if you give it a True up front, it does not issue the warning. I have used this before too and it has it's advantages, as you point out, but I guess I was looking for a stricter case. – Mike Jun 9 '16 at 17:24
  • 9
    If the consumer of the API says up front, "I want to do this even if it's dangerous", I don't see any reason for the API to second-guess them. The API should just do as it was asked. – Tanner Swett Jun 9 '16 at 17:32
  • @Mike: I see your point. In that case I'd still be inclined to return rather than throw (but return something more sophisticated), just because I find the flow of control more logical and easier to follow months or even years later. – Martin Kochanski Jun 9 '16 at 17:34
  • @MartinKochanski if this is the approach taken, then the UI developer could simply pre-fill the needed "do it anyway" string and your solution fails to solve the human issue. The API is not the place for second guessing the client. – Max Sorin Jun 9 '16 at 20:26
  • No, the original idea allowed pre-filling, but the modified one does not. Ultimately it depends what you want. I originally needed to be able to store credentials. If I hadn't, I would have returned an object from the API - "call this if you're certain, discard it if not". That's because I work across 4 different architectures and find object return less exigent than throwing. It doesn't require certain compiler options, for a start. But if you're happy throwing, throw. – Martin Kochanski Jun 9 '16 at 20:39
5

Confirmation is not really appropriate for an API. The point of "are you sure?" confirmations is to allow a human user to think twice before performing a potentially destructive operation. An API is called by code, so any form of confirmation will have to programmed into the client from the beginning, thereby making the warning moot.

You suggest the confirmation could be passed on to a user interacting with the client. But this breaks separation of concerns. The whole point of an API is that it does not care if it is called as part of a manual operation or if it is called through a script or whatever.

So forget the "confirmation token", and instead design the API in such a way that it communicates clearly to client developers that a certain operation is potentially dangerous. For example, you might have two version of a call:

DriveForwardIfSafe()
DriveForwardIgnoreAllWarnings()

This communicates very clearly to the client developer that one version is potentially unsafe, and the developers deciding to call the "unsafe" version will be doing so informed.

0

Confirmation dialogs aren't normally an API concern, they're a UI concern, because it pertains to letting a fallible, inconsistent human change their mind or correcting for mis-keys and mis-clicks before committing their actions.

That said, there are exceptions to the norm.

Many services provide history and/or undo functionality, for example. In the case where the API is far removed from the actual end user, or when the interactions themselves need to be fluid and efficient, store some level of history and permit undo operations.

And if, for whatever reason, the operation were totally irreversible, like launching a missile, your API should demand an authorization code, possibly a single-use code -- and possibly from multiple users. But, that would likely be collected during the initial dangerous call -- you wouldn't throw an exception in to warn the user of the danger. The presence of the auth token signifies that.

-1

I would require an additional grant for the operation. E.g. the operation can only be executed if you have su. And su can only be granted temporarily. So to invoke this operation you would need to request temporary su permission first then invoke the operation before su expires.

  • 1
    Does this really accomplish anything though? As the answer by @JacquesB points out, this is getting called from code, so permissions, multiple API calls, etc. are just eating up CPU cycles. The client is going to be programmed do to whatever it needs to do so having 2 or 15 extra steps doesn't really make a difference. – Dan1701 Jan 2 '17 at 6:21

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

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