Code formatting

Code formatting refers to the arrangement and presentation of code in a consistent and organized manner. Just as well-formatted documents enhance readability and comprehension, properly formatted code contributes to the clarity and maintainability of software projects. Consistent formatting practices not only make your code visually appealing but also promote collaboration among developers by providing a unified structure.

On this page we will delve into the essential aspects of code formatting, including proper indentation, spacing, and comments. These formatting guidelines help create a cohesive visual structure that aids in understanding the code's logic flow and intentions. By adhering to consistent formatting standards, you'll contribute to a codebase that is not only functional but also approachable, making it easier for developers to contribute, debug, and enhance the software over time.

Spacing and whitespace

By using spacing and whitespace in your code you can improve the readability.
Spacing or whitespace can be a space, tab or enter and it doesn’t leave a visible mark in your code. Indentation is also an example of adding whitespace (for more information on indentation see below).

Adding spacing between, for example, your logic tags and its content or before and after operators makes your code easier to read.
For example:

Don´t do: 
{%assign test="content"%}
{%if test!=variable_1%}

Do:
{% assign test = "content" %}
{% if test != variable_1 %}

There is an exception when using HTML tags, for example in an HTML table. There can’t be any spacing between the opening character and the element, because this will lead to errors in your HTML table.
It is good practice to add some spacing between your attributes within your HTML tag however.
For example:

Don´t do:
< td >content< /td >
<td class="usr-align-right"colspan="2">content</td>

Do:
<td>content</td>
<td class="usr-align-right" colspan="2">content</td>

More information about HTML (tables) in STL can be found here.

📘

Snippets

You can use hotkeys to add tags. For example type assign and hit tab and the editor will create:
{% assign variable = content %}
The spacing in these snippets is already correct!

Adding an entire whitespace (or blank) line in your code can be used to distinguish sections of your code easier. This way it becomes clearer where each section starts and ends.
For example:

Don´t do:
{% t= "first_translation" nl:"Eerste vertaling" default:"First translation" %}
{% t= "other_translation" nl:"Een andere vertaling" default:"An other translation" %}
{% t "first_translation" %} {% input custom.some.other_thing as:currency %}
{% assign variable_first_section = 100 %}
{% assign outcome_first_section = variable_first_section+custom.some.other_thing %}
{% result 'first_result' outcome_first_section %}
{% input custom.some.other_section as:boolean %}
{% if custom.some.other_section == true %}{% t "other_translation" %} {% input custom.second.thing as:currency %}{% endif %}
{% result 'second_result' custom.second.thing %}

Do:
{% comment %}translations{% endcomment %}
{% t= "first_translation" nl:"Eerste vertaling" default:"First translation" %}
{% t= "other_translation" nl:"Een andere vertaling" default:"An other translation" %}

{% comment %}first section{% endcomment %}
{% t "first_translation" %} {% input custom.some.other_thing as:currency %}
{% assign variable_first_section = 100 %}
{% assign outcome_first_section = variable_first_section+custom.some.other_thing %}
{% result 'first_result' outcome_first_section %}

{% comment %}second section{% endcomment %}
{% input custom.some.other_section as:boolean %}
{% if custom.some.other_section == true %}
  {% t "other_translation" %} {% input custom.second.thing as:currency %}
{% endif %}
{% result 'second_result' custom.second.thing %}

You can also add whitespace at the end of your line, this is called trailing whitespace. This type of whitespace is unnecessary and can even lead to errors in your code, so it is best to avoid this.
Don't do this:

Indentation

Indentation refers to the spaces at the beginning of a line of code. In some programming languages it impacts code readability only, while in others it can be essentials for code blocks to function properly.

We've gathered some examples and exceptions to keep in mind while coding. As a general rule all code should be indented. Your indentation should take no less than 2 spaces. If you want to increase readability even more, you can utilize whitespace or comments, but not double indentation.

In Liquid, you use indentation as follows:

{% assign show_message = "indenting is great" %}
{% for item in collection %}
  {% if item == true %}
    {% ic %}
      {{ show_message }}
    {% endic %}
  {% endif %}
{% endfor %}

There are some specific scenarios to take into account.

  1. Case/when: when should be indented on the same level as case
{% case fiscal_year %}
{% when 2018 %}
  {% assign ratio = 0.23 %}
{% when 2019 %}
  {% assign ratio = 0.27 %}
{% else %}
  {% assign ratio = 0.19 %}
{% endcase %}
  1. Liquid tags within HTML tables.

    Generally the Liquid & HTML tags work together in terms of indentation.
    There are however a few exceptions.

    1. If there is a single item in the <td> or the HTML tag is empty, don't indent it.
      If there is more than one liquid tag, indent it.

    2. HTML tags that replace former markdown formatting for bold, italic etc. such as <b> and <strong> don't need indentations

      One tag, no indentation
      <tr>
        <td>{{ item | currency }}</td>
        <td>Jack Russell</td>
      </tr>
      
      Multiple tags, indentation.
      <tr>
        <td>
          {% for item in my_collection %}
            {{ item.value }}
          {% endfor %}
        </td>
        <td><b>Jack Russell</b></td>
      </tr>
      
  2. No indentation allowed for logic statements within capture tags. At least if you're capturing something that needs to be printed later on, as this will mess up spacing in the template itself.

    1. When capturing a single item (a tag or string), there's no need to indent it either.
  3. Comments tags don't need to be indented.

  4. Some tags are indentation sensitive and should therefore not be indented.

    1. {% newpage %}
    2. group tags
    3. Warningtexts
    4. XBRL (indentation serves function and not readability!)

Quotes

You can define strings by using quotes.
While both single and double quotes are supported, the convention is to always use double quotes.

❗️

Combinations of single and double quotes in the same string will not work, and omitting the opening or closing quote will not work either.

Don't do:
{% t= "car" nl:"wagen" fr:"voiture %}
{% t "car' %}

Do:
{% t= "car" nl:"wagen" fr:"voiture" %}
{% t "car" %}

Some examples of where you'll need to use quotes while coding:

arrays
{% assign used_acc_numbers = "610000;610015;610023;610045" | split:";" %}

date & time
{{ "31/12/2018" | date:"%Y" }}

HTML table styling
<table class="">
  <tbody>
    <tr>
      <td class="">content</td>
      <td class="">content</td>
    </tr>
  </tbody>
</table>

Note that in HTML double quotes are used to define table styling, such as width, indentation, colspan etc.

Variable naming

Intro

CONSISTENT - READABLE - MAINTAINABLE

If we have one thing in common as developers, it probably is the struggle to define how to name the damn variable or custom input-field. 🤬

In the midst of jotting down lines of code, you could be tempted to use these baddies: var_1 , reg_2 , DeprAsset , asset_cat4 , TaxRate1 ...

Even though you may have the intention to rename these variables later on to more descriptive ones, or maybe you even think these variables make sense... 🤷 But as the Dutch saying goes: “good rules, good friends”

Your main goal is to be consistent in applying naming conventions to ensure readability and maintainability of your code. Always keep in mind that your code will be read by other developers and should thus be easy to understand.

Spelling

General best practice entails using English throughout your code. At Silverfin we opt for UK English, given our location. But in software development American English is more common, so it's up to you to decide which spelling you wish to commit to.

Don't do:
{% input custom.langlopendeschulden.text default:langlopendeschulden_default %} 
{% t= "t_organized_events_text" %}

Do:
{% input custom.long_term_debts.amount default:long_term_debts_default %}
{% t= "t_organised_events_text" %}

Style

In terms of style or format of variable names, there are just a few key conventions to keep in mind.

🤝 always use Snake Case
🤝 always use lower case
🤝 never use numbers at the beginning of your variable
🤝 never use special characters like “@£$%&”
🤝 avoid using hardcoded numbers to distinguish between variables

Don't do:
{% assign MyVariableName = "Investments" %}
{% assign 2_BankAccount = "550002" %}
{% assign US$_BankAccount = "550002" %} 

art_privacy_1
art_privacy_2

Do:
{% assign my_variable_name = "investments" %}
{% assign second_bank_account = "550002" %}
{% assign usd_bank_account = "550002" %}

art_privacy_employers
art_privacy_employees

Abbreviations

The general rule of thumb should be: don’t shorten names unless you really need to. It only makes sense if the variable name is super long. Ideally, a variable name should have at least 8 characters.

You should also stick to abbreviations that are widespread and frequently used in Silverfin. An example is the well-known abbreviation of “current year“ as ”cy“ and ”previous year” as ”py“.

cy_value  
py_value

A good reason to avoid unnecessary abbreviations is debugging. Imagine searching for a variable called tr in a template that contains HTML-tables, you would get hundreds of hits.

Don't do:
rf_date  
tr
Y_end
{% for i in classes_array %}

Do:
rollforward_date
tax_rate
year_end
{% for item/index in classes_array %}

Result naming

No matter how tempted you are, don’t use result_ as a prefix! It's bad practice to use a tag as part of your variable name and will again make debugging more cumbersome. Keep in mind that these results are not only used by developers, but also by the clients via Insights. You should make them descriptive.

Don't do:
{% result "result_depr_by" $5 %}

Do:
{% result "total_depreciations_bookyear" total_depreciations_bookyear %}

It's common practice in Silverfin to use the same variable name for the result as for the variable it contains.

Custom variable naming

All the above also applies to naming custom variables. As these serve as entry points for the database, it's even more important that they follow proper naming conventions. Custom variables can never be renamed as that will result in breaking details. Think before you code 🧠

Don't do:
{% input custom.voorderingenopbestuurdersencomissarissen.text as:text %}
custom.ValueYearEnd.value

Do:
{% input custom.amounts_receivable_directors_commissioners.note as:text %}
custom.total_year_end.amount

Additionally, it's important not to use the same namespace for your custom variable as one you're already using for a custom collection (fori) in the same template.

Dynamic variables

In many cases, dynamic variable naming depends on other inputs and should thus be built according to the same principles. In case you’d need to use the persistent_id or any other numeric value, you should add a comment that details the structure.

Don't do:
{% capture title %}{{ varia.title }}{% endcapture %}

{% assign current = current_reconciliation.handle %}
{% assign privacy_2 = custom.[curr_rt].privacy_2 %}
{% assign privacy_3 = custom.[current].privacy_3 %}

Do:
{% comment %}The dynamic variable "directors_id" is built as follows to ensure unique values: director_{{ person.id }} resulting in: director_4658423{% endcomment %}
{% capture directors_id %}director_{{ person.id }}{% endcapture %}

{% capture title %}title_{{ varia.key }}{% endcapture %}

{% assign current_template = current_reconciliation.handle %}
{% assign privacy_art_employees = custom.[current_template].privacy_art_employees %}

Note how you shouldn't use the content of an custom variable. In case of iterations, you should use the key instead.

Parts

When using different parts, we recommend only using small letters, numbers and underscores for part names. The same general conventions apply as for local variables.

🚧

Part naming

Make sure that the name of the part is different from that of any other (local) variable.

Handles

Reconciliation text handles should be descriptive or should be the abbreviation of the template’s name. Furthermore a handle has to be unique, may not contain “.” and can only exist out of lower case letters and underscores. General conventions for variable naming apply.

Comments

It’s always the goal to write clear code, and keep it as simple as possible. If so, you could argue whether it's really necessary to add comments.
That's a fair point, but you will not be the only one that will read your code. Other developers will jump into the code for the very first time, to do some maintenance or required updates. So in general it’s beneficial to add comments to make everything clear as possible.

Programs (such as Silverfin) will ignore comments and just execute the code, while humans are different, and require additional context to the coding

Even if the logic and naming conventions are followed properly, comments can already help to make code stand-out between everything else too (if the coding is clear enough, most will ignore the comments anyway but they can still help to order and structure the coding between everything else).

Example:
{% for item in inventory_array %}

  {% comment %}create needed vars{% endcomment %}
  {% include "parts/create_keys" %}
  
{% endfor %}

Thanks to the comment in the above snippet, the inclusion of a new part stands out more. Otherwise that single line of code almost "floats" between the rest.

💡Comments help others understand the code better
💡Comments are used to structure the code

Comments for documentation

When documentation is stored externally (Google Docus, Balsamiq etc.), you're not certain that the person that has to read the code in the future, has access to that documentation (let alone knows where to find it).

👍

If you store the template code in a repository, you should document everything in the readme file.

If you don't have a clear cut way of storing documentation it's good practice to start every template with a comment block explaining the goal of the template; what it is used for, if there are specials things about it...

A good comment could be that the template creates certain results that are used somewhere else, and thus inform the reader of the code that these cannot be modified unless checking the output and impact on the other template.

Example:

{% comment %}
==========================================================================================================
                                    ----------------------------------
                                    LUX AA SETTINGS DOES IT ALL - HOW?
                                    ----------------------------------

This RT creates most of the LUX AA logic, in the following parts: 
  1/  impact_aa
      it creates some vars that are needed in others (address, chosen depreciation, ...)
        ->  created as result in part "sf_insights"
            ->  can be accessed for other RT's in shared part "lux_aa_size"
        ->  db vars are accessed directly 
  2/  size_calculation
      it creates the modal of BS, P&L and Acc Notes 
        ->  created as result in part "sf_insights"
            ->  can be accessed for other RT's in shared part "lux_aa_size" 
  3/  agent_values
      it creates certain values needed to create the XML properly
        ->  created as result in part "sf_insights"
  4/  acc_notes
      it creates AUTO HIDE of all acc notes, the acc note overview and its logic, link eCDF fields
        ->  auto hide vars, array acc note overview are created as results in this part
      the acc note numbers are also created there
        ->  these are created as dyn vars as well which are then pushed to results
  5/  ref_creation
      it creates for every eCDF field which acc note needs to be linked (see part create_ref_vars)
        ->  created as results in part "add_handle_to_ecdf" (the eCDF RT's will access it from there)

==========================================================================================================
{% endcomment %}

Comment placement

Comments are used to add context to the code, so we advise to put comments on a new line before the actual code.

You may also see in-line comment; comments after the actual code; but many readers consider these to be confusing. Avoid them.

It's also best to keep the comment close to the code, so don't add an empty line in between.

Don't do:
{% for item in inventory_array %}
  
  {% include "parts/create_keys" %}{% comment %}create needed vars{% endcomment %}
  
{% endfor %}

OR
{% for item in inventory_array %}

  {% comment %}create needed vars{% endcomment %}
  
  {% include "parts/create_keys" %}
  
{% endfor %}

Do:
{% for item in inventory_array %}

  {% comment %}create needed vars{% endcomment %}
  {% include "parts/create_keys" %}
  
{% endfor %}

Have a look at the example below to learn how comments can help to visualize the structure of your code.

{% comment %}
------------------------
  PERIOD SPECIFIC VARS
------------------------
{% endcomment %}
{% comment %}end date year{% endcomment %}
{% assign enddate_year = period.year_end_date | date:"%Y" %}
{% comment %}beign date year{% endcomment %}
{% assign begindate_year = period.year_start_date | date:"%Y" %}

{% comment %}
------------------------
  COMPANY RELATED VARS
------------------------
{% endcomment %}
{% comment %}company name always need to be set in UPCASE{% endcomment %}
{% capture comp_name %}{{ company.name | upcase }}{% endcapture %}
{% comment %}comp vat number{% endcomment %}
{% assign comp_vat_nbr = company.vat_identifier %} 

Comment formatting

As you can see in the example above, there are some ways to make comments stand out more.
Special characters, such as =, < or >, -, ... can help make the comment be more attractive to read or catch the eye.
Some examples:

{% comment %}
===================================================================================
                                -------------------
                                ABOUT THIS TEMPLATE
                                -------------------

This template is used to create the account notes of the workflow Annual Accounts; 
in here we create: 

  */  the order of the notes
        ->  created in part "create_order_notes"
  */  the AUTO HIDE FORMULA of each note
        ->  created in part "auto_hide_notes"
===================================================================================
{% endcomment %}

{% comment %}
=================
  DETAILS TABLE
=================
{% endcomment %}
<tr>

  {% comment %}
  -----------------
    Opening value
  -----------------
  {% endcomment %}
  {% comment %}access keys creation{% endcomment %}
  {% include "parts/creation_keys" %}
  
  {% comment %}create OPE value{% endcomment %}
  {% assign opening_value_inv_fa = period.accounts.include_zeros | range:inv_ope_range %}
  {% assign opening_value_inv_fa = opening_value_inv_fa.opening_value %}
  
  {% comment %}assign to TOTAL{% endcomment %}
  {% assign [tot_inv] = [tot_inv]+opening_value_inv_fa %}

  <td class="">{{ opening_value_inv_fa | currency }}</td>
  
  {% comment %}
  ----------------------
    Depreciation value
  ----------------------
  {% endcomment %}
	...

</tr>

Writing comments

Last but not least, here are some conventions to keep in mind while writing the actual comment:

  • Keep it as short as possible

    • Long comments might cause the reader to lose interest
  • Avoid complexity at all costs

    • If a comment is even too complex to understand, chances are the coding will be even worse
  • Write your comments first, and then your code

    • Writing comments upfront makes you consider how best to structure your code
  • Write for others, not yourself

    • Make sure others can understand your comments, so avoid e.g. personal abbreviations
  • Don’t duplicate the code

    • Don’t comment that an IF-statement is starting, but write why that IF-statement is needed there
  • Avoid confusion

    • If there is even the slightest chance of confusion, do not use the comment (or re-write it)
  • Use comments when the original code is altered

    • this will help clarify why an update was done (so this could mean changing the original outdated comment, or adding a new one)
  • Document limitations

    • if certain logic can not be done (even if someone might expect it), due to limits in Liquid or other, write it briefly in a comment

Structure

Lastly, if all templates follow a similar structure the code will be easier to maintain. You've seen how comments can be used to add structure, but here are a few more tips that will help you keep your code consistent.

Parts

Parts are essential if you want to keep your code readable. They are also the perfect tool to add some structure.

  1. Part 1 should always contain your translation definitions, that way they are grouped nicely together and kept separate from the rest of the code base. Making it a lot easier if the translation definitions need to be shared with a translator, or when some wording needs to be updated.
  2. When texts that are reused across different templates you should use a shared part to ensure the phrasing is consistent across the template set.
  3. When you're dealing with big calculations, you can add them to a part to separate them from the rest of the code base.
  4. Content updates for financial statements or tax solutions are commonplace. The best way to deal with them is by using parts. You can create a new part for every fiscal year that contains the relevant code that is applicable. That way your legacy code remains in place, but won't be impacted by this year's updates.
{% comment %} Display the relevant code depending on tax years {% endcomment %}
{% if period.fiscal_year == 2021 %}

  {% include "parts/ty_2021_code" %}
  
{% elsif period.fiscal_year == 2022 %}

  {% include "parts/ty_2022_code" %}

{% elsif period.fiscal_year == 2023 %}

  {% include "parts/ty_2023_code" %}

{% elsif period.fiscal_year == 2024 %}
  
  {::infotext}
    {% t "current_ty_not_yet_published_t" cy_fiscal:cy_fiscal %}
  {:/infotext} 
  
  {% include "parts/ty_2023_code" %}

{% else %}

  {% comment %} Any other tax year {% endcomment %}
  {{ wrong_taxyear }}

{% endif %}

👍

Add an overview

When you are using parts, it's good practice to add a comment on top of the template that explains which parts (local and shared) are being used and how.

You can find more information related to shared parts in the section on Architecture.

Group your code

Another way to add structure to your code is by grouping certain pieces together and by separating logic and markup.

  • Group general variables on top of your code or even in a separate part
    • By creating your local variables first, you're less likely to create duplicate ones
  • Group rollforward logic
    • Grouping the rollforward logic helps to make you more mindful of the functionality
  • Group results
    • Preferably on the bottom of the main part, so they can be accessed easily via Insights

Debugging becomes a lot easier when you separate logic from markup. Wherever possible, you should code the logic first, and then the table. That way it becomes easier to locate where content is actually being printed.
In most cases, the logic should also stay outside of the ic/nic-tags.
This approach will also help in avoiding repeated logic.

{% fori person in period_people %}

  {% comment %}create needed vars for each person{% endcomment %}
  {% assign director = person.director | default:false %}
  {% assign shareholder = person.shareholder | default:false %}
  {% assign nbr_people = nbr_people | plus:1 %}
  
  {% comment %}in case of a branch, make sure shareholders cannot be entered{% endcomment %}
  {% if branch %}
    {% assign shareholder = false %}
  {% endif %}
  
  {% comment %}create string with their chosen role{% endcomment %}
  {% assign txt_roles = "" %}
  {% if director %}
    {% capture txt_role %}{% t "director_role" %}{% endcapture %}
    {% assign role_text = txt_role | split:"," %}
    {% assign txt_roles = txt_roles | concat:role_text %}
  {% endif %}
  {% if shareholder %}
    {% capture txt_role %}{% t "shareholder_role" %}{% endcapture %}
    {% assign role_text = txt_role | split:"," %}
    {% assign txt_roles = txt_roles | concat:role_text %}
  {% endif %}
  {% capture txt_roles %}{{ txt_roles | join:", " | remove_first: ", " }}{% endcapture %}  
  
  {% comment %}
  --------------------
    NAME PEOPLE DROP
  --------------------
  {% endcomment %}
  <table class="usr-width-100">
    <tbody>
      <tr>
        <td class="usr-line-bottom usr-width-75">
          {::font size='m'}**( {{ nbr_people }}. )**{:/font} 
          {% if person.name != blank %}
            {::font size='m'}**{{ person.name }}**{:/font}
            {% nic %}
              {% if txt_roles != blank %}
                {{ some_indent }}
                *{{ txt_roles }}*
              {% endif %}              
            {% endnic %}
          {% endif %}            
        </td>
        <td class="">&nbsp;</td>
      </tr>        
    </tbody>
  </table>
{% endfori %}    

What’s Next