Fail Fast Programming

Advanced Programming Practises To Reduce Bugs

What is Fail Fast Programming

Fail Fast Programming is the practise of verifying that all business rules have been properly satisfied before performing a function's requested task. If one of the business rules are not met an exception is thrown (and properly handled upline) and the function does not perform its requested task. This is to exclude dirty data – quite often bugs that occur in production are due to the allowance of dirty data into the database.

The tests are performed at the 'entrance' to the function, much like a 'doorman' checking for valid credentials – hence the term 'fail fast' – you will end up with many try/throw/catch tests at the top of your function.

When Should Fail Fast Programming Be Implemented

Methods should follow the "fail fast" approach to programming whenever applicable.

Values should be verified before proceeding.

Verification differs from validation, which ensures that the data type and value range is correct. Verifying values means ensuring that they are contextually appropriate. Often these are foreign-key relationships to the passed-in object.

Verification is performed by locating them in the database to ensure that they exist. This prevents saving values to foreign keys that do not actually exist.

If they DON'T exist, an appropriate custom exception should be thrown (and caught upline before the response is sent).

If they DO exist, the next step is verifying that the values relate accordingly. This is determined by business rules which should be in the ticket description, and generally the responsibility of the Implementation Lead to specify, as they are the 'client' to the Engineering team. In the event that business requirements are not clearly defined in the ticket it is the responsibility of the assigned developer to gather those requirements BEFORE beginning the task.

An example of business rules:

To publish a Lottery:
The user must have authorization to 'create' lotteries
The lottery should be 'draft' status
The schedule draft status should be 'draft' status
Each stage of the lottery should be 'complete'

Without considering the business rules we might end up with the following code:

public function publish(Lottery $lottery)
    {
        $this->service->publish($lottery);
        
        return (new EnvelopedResponse)
            ->setStatus(ResponseStatus::SUCCESS)
            ->setData(PublishLotteryResource::make(
                $lottery
            ));
    }

This is called 'happy path' programming and is a bad habit to carry as a developer – it leads to invalid conditions in the database and eventually a bug ticket would be opened by an end user to correct the inconsistencies in the system's behaviour.

In this example, an end user could inadvertently select a lottery in the incorrect stage and publish it.

By obtaining the business rules the developer could create the following fail fast checks:

public function publish(PublishLotteryRequest $request, Lottery $lottery)
    {
        $this->authorize('create', new Lottery());
        try {
            //check the status of the lottery to avoid generating a duplicate schedule
            if ($lottery->status != Lottery::STATUS_DRAFT) {
                throw new InvalidStatusException($lottery->lottery_id);
            }

            //check the status of the draft
            $draft = $lottery->draft()->first();

            if (is_null($draft) || $draft->status != ScheduleDraft::STATUS_DRAFT) {
                throw new InvalidStageStatusException($lottery->schedule_draft_id);
            }

            //check to ensure each stage is complete
            $stages = $lottery->stages()->get();
            if (count($stages) == 0) {
                throw new UnassignedStagesException($lottery->lottery_id);
            }
            foreach ($stages as $stage) {
                if ($stage->status != Stage::STATUS_CLOSED) {
                    throw new InvalidStageStateException($stage);
                }
            }
            //still here? publish the lottery
            $this->service->publish($lottery);

            return (new EnvelopedResponse)
                ->setStatus(ResponseStatus::SUCCESS)
                ->setData(PublishLotteryResource::make(
                    $lottery
                ));
        } catch (\Exception $exception) {
            return (new EnvelopedResponse)
                ->setStatus($exception->getCode())
                ->setData($exception->getMessage());
        }
    }

The key to fail fast programming is to check all conditions BEFORE attempting to satisfy the request.

From the above example we can see the following fail fast checks for errors based on business requirements:

  • The lottery may be already published or is on hold – either way it's not in a stage that it can be published right now

  • The lottery's schedule draft status may already be live and not in the required 'draft' status

  • The lottery may have no stages assigned yet

  • One or more of the lottery's stages may not be completed so the lottery cannot be published while in an 'incomplete' stage

These steps to verify the business requirements MUST be followed before saving the posted form.

Where Should Fail Fast Programming Be Used

Fail fast does not just occur in the Controller, but in EVERY function called in every class.

This includes:

  • Services

  • Handlers

  • EventListeners

  • Filters

Whether to pass the Exception to the calling class or handle it locally is determined by the context of the function's purpose.

Last updated