# Preconditions and TryFunctions in AL (Business Central)

_Published: 2024-01-12 · Author: Finn_

## Preconditions and TryFunctions in AL

TryFunctions in AL deserve more attention. They can play an important role in making your code more safe, robust, and easier to read.

## A little bit of Theory and Terminology

A function *f* maps a domain *A* to its codomain *B*.

$$
f: A \rightarrow B
$$

The function *f* takes an argument *a* and returns a value *b*.

$$
f(a) = b
$$

This article is about ensuring that the argument *a* is within the domain *A* of the function *f* before running the function. This is a **precondition** for running the function.

Automated Testing is about ensuring that the value *b* corresponds with the expected mapping into the codomain *B*.

## Checking Preconditions

Before looking at the TryFunction, let us look at a simple case of ensuring that the preconditions for running the function are met.

We have been asked to make an average function given a total and a number of lines.

That is easy, we think and write the following.

```al
procedure Average(Total: Decimal; NumberOfLines: Integer): Decimal
begin
    exit(Total / NumberOfLines);
end;
```

*This is an example of code that is not robust.*

Except that we are not checking the preconditions. The preconditions for this function
is that the NumberOfLines is a positive number. I often write the precondition check in the following format at the start of the function:

```al
    if not (Precondition) then
        exit();
```

Like this:

```al
procedure Average(Total: Decimal; NumberOfLines: Integer): Decimal
begin
    if not (NumberOfLines > 0) then
        exit(0);

    exit(Total / NumberOfLines);
end;
```

But if a developer calls this function with a negative number of lines, something is wrong.
So I want to insist harder on the valid domain for the function, and I throw an error.

```al
procedure Average(Total: Decimal; NumberOfLines: Integer): Decimal
var
    ErrorLbl: Label 'Number of Lines cannot be a negative number.';
begin
    if (NumberOfLines < 0) then
        Error(ErrorLbl);
    if (NumberOfLines = 0) then
        exit(0);

    exit(Total / NumberOfLines);
end;

```

I could go further and check that the Total should be zero if the NumberOfLines is zero.
And I could avoid the calculation and possible rounding errors when the NumberOfLines is one.

```al
procedure Average(Total: Decimal; NumberOfLines: Integer): Decimal
begin
    if (NumberOfLines < 0) then
        Error('Number of lines cannot be a negative number.');
    if (NumberOfLines = 0) then begin
        if not (Total = 0) then
            Error('The total must be zero, when the number of lines is zero.');
        exit(0);
    end; 
    if (NumberOfLines = 1) then
        exit(Total)
    else
        exit(Total / NumberOfLines);
end;
```

*This is an example of code that is robust.*

## TryFunctions in AL

Now let us consider a standard system function in AL:
[System.DMY2Date](https://learn.microsoft.com/en-us/dynamics365/business-central/dev-itpro/developer/methods-auto/system/system-dmy2date-method)

```al
Date := System.DMY2Date(Day: Integer, Month: Integer, Year: Integer);
```

The domain is

- Day: Integer between 1 and 31
- Month: Integer between 1 and 12
- Year: Integer between 0 and 9999

But checking that each argument is within its domain is not enough to prevent the function from failing hard during runtime.

```al
Date := System.DMY2Date(29, 2, 2019);
```

*February 29th, 2019 is not a valid date.*

Imagine that you are getting text data from an import file or an API, and you have to convert it into a date.

To protect our code, we add the [TryFunction Attribute](https://learn.microsoft.com/en-us/dynamics365/business-central/dev-itpro/developer/attributes/devenv-tryfunction-attribute) to our function. The TryFunction cannot have a return value, since it default returns a boolean.
So we add `var` to return the calculated value `NewDate` as an argument.

```al
[TryFunction]
procedure TryDmy2Date(Day: Integer; Month: Integer; Year: Integer; var NewDate: Date)
begin
    NewDate := DMY2Date(Day, Month, Year);
end;
```

This leads us to answer a very important question. What should we do if the function fails?

For this, I develop helper functions like these.

```al
procedure IsDmyValidDate(Day: Integer; Month: Integer; Year: Integer): Boolean
var
    NewDate: Date;
begin
    exit(TryDmy2Date(Day, Month, Year, NewDate))
end;

procedure Dmy2DateWithDefault(Day: Integer; Month: Integer; Year: Integer; DefaultDate: Date) NewDate: Date
begin
    if not TryDmy2Date(Day, Month, Year, NewDate) then
        NewDate := DefaultDate;
end;
```

The `IsDmyValidDate` function allows the developer to check if the information is valid or not.

The `Dmy2DateWithDefault` function forces the developer to decide what date should be returned in case of failure. Maybe the default value is the `0D`, or maybe `Today()` or something else.

## Explained by Erik Hougaard

Yesterday (January 29, 2024) Erik posted this video on YouTube: [Is your AL code robust or weak, what kind of developer are you with Business Central?](https://www.youtube.com/watch?v=v3KS80NhOec)

Erik walks you through the [Microsoft Learn: Failure modeling and robust coding practices](https://learn.microsoft.com/en-us/dynamics365/business-central/dev-itpro/developer/devenv-robust-coding-practices) page.

Checking your preconditions is a good example of "Don't trust consumers of your code" and my TryFunction example speaks for itself.

## Elm Language and Maybe

This approach is inspired by the [Elm Language](https://elm-lang.org/).
Elm is a delightful language for reliable web applications.

Elm can add [Maybe](https://guide.elm-lang.org/error_handling/maybe) to a domain type like `Float`.

The `Maybe` type is defined as

```elm
type Maybe a
    = Just a
    | Nothing
```

**Example:**

Convert a String to a Float.

```elm
> String.toFloat
<function> : String -> Maybe Float

> String.toFloat "3.1415"
Just 3.1415 : Maybe Float

> String.toFloat "abc"
Nothing : Maybe Float
```

The `Dmy2DateWithDefault` function above is inspired by the [Maybe with Default](https://package.elm-lang.org/packages/elm/core/latest/Maybe#withDefault).

We do not have a Maybe type in AL, but we have TryFunctions to keep us safe.

## Clean AL Code Initiative

- Part 1: [Rulesets in Business Central](/programming/2023/10/10/BC-Rulesets.html)
- Part 2: [Namespaces in AL](/programming/2023/12/28/Namespaces-in-AL.html)
- Part 3: [VS Code Extensions for AL](/programming/2023/12/29/Extensions-for-AL.html)
- Part 4: [Automated Tests in AL](/programming/2024/01/03/Automated-Tests-in-AL.html)
- Part 5: [Advanced CodeCop Analyzer and Custom Rulesets](/programming/2024/01/05/BC-Advanced-Rulesets.html)
- Part 6: [How to make a code review](/programming/2024/01/11/how-to-make-a-code-review.html)
- **Part 7**: [Preconditions and TryFunctions in AL](/programming/2024/01/12/TryFunctions-in-AL.html)
- Part 8: [There is a new sheriff in town](/programming/2026/07/22/there-is-a-new-sheriff-in-town.html)

## Super fast tests covering 100% of your code

- Part 1: [Environment Interfaces - Part 1 - System Environment](/programming/2025/06/26/environment-interface-part-1.html)
- Part 2: [Environment Interfaces - Part 2 - External API](/programming/2025/06/26/environment-interface-part-2.html)
- Part 3: [Environment Interfaces - Part 3 - Standard Application](/programming/2025/06/26/environment-interface-part-3.html)
- Part 4: [Temporary Tables in Tests](/programming/2025/06/26/temporary-tables-in-tests.html)

---

[View the normal web page](https://www.finnpedersenfrance.com/programming/2024/01/12/TryFunctions-in-AL.html)
