World Of Bugs
There are 1.4 billion insects for every human, most of them on your code
The concepts on this post are useful for any language, examples are from Nim.
Defensive Programming usually takes the form of a Unittest,
Unittest checks a "unit" of code, validates using assertions on special test runs,
runs a code block and asserts that the result equals a predefined "sample" result.
This is good, but is has some limitations,
the input, data and context is very often faked (mocked),
the input is usually not asserted because is kinda fixed,
the output is result of the inputs so neither is completely real world,
it runs on "dedicated" automated tests runs.
- How can tests be more integrated and close to the code where the bug lives?
Side Effects
Bugs is the entropy of the universe trying to reverse engineer your code while you are still writing it
Side Effect Free programming is focused on eliminating side effects on the code,
and monitoring or controlling them where you can not eliminate them.
Functional programming tries to reduce Side Effects,
without Side Effects the Bugs can not spread on the code, helps reducing Bugs.
Side Effects examples:
- Writing to terminal or files, reading from terminal, files or user input.
- Mutating in-place of variables, globals, objects, types, data structures.
- Date/time functions, Win32 API/Registry, Databases, HTTP, GUI, exceptions.
Nim has Side Effects Tracking, Functional advantages while still using OOP.
Simply changing proc
to func
will check for side effects on your functions!.
Immutability
Immutability changes everything!
Immutability of variables helps to fight Bugs by not allowing unwanted mutations,
using Immutability whenever possible is very encouraged.
Nim is immutable by default and has 3 kinds of variables:
-
let
its a runtime immutable variable. -
const
its compile-time immutable variable. -
var
its runtime mutable variable.
Contracts
TDD is Poor-Man's Contracts
Some programming languages use a different way of dealing with all this,
languages like Ada or Eiffel, use Design by Contract. Applied Hoare logic.
Contracts can assert on input, data and context on the beginning of a code block,
and can also assert on the result at the end of a code block,
it lives in the code block as close as possible to it, it runs an actual real run,
everything is real inputs, data, context and whatnot,
on programming languages that support compile-time code execution all this can happen at compile time if desired.
Contra
The night is dark and full of errors
Contra reimagines Design By Contract in a modern KISS way,
all the benefits of Assertive Programming on statically typed compiled language,
like everything on Nim it takes a lot of care about performance,
it allows fast contracts with zero cost at runtime with a comfy API,
requires minimal modifications on existing code, just 2 lines to add a Contract.
Lets take a buggy example code and add a Contract to it:
proc myFunction(mustBePositive: int): int =
result = mustBePositive - 1
Remember that mustBePositive
must be Positive, lets use the function:
proc myFunction(mustBePositive: int): int =
result = mustBePositive - 1
echo myFunction(0)
This is a Bug, mustBePositive
is not Positive (0 - 1
is -1
), lets add Contra,
and ensure that there are no Side Effects using func
:
import contra
func myFunction(mustBePositive: int): int =
preconditions mustBePositive > 0 # Require
postconditions result > 0 # Ensure
result = mustBePositive - 1
echo myFunction(0)
The Contract shields our code from the Bug, wont allow the Bug to spread.
-
preconditions
takes preconditions separated by commas, asserts on arguments or local variables. -
postconditions
takes postconditions separated by commas, must assert onresult
, can assert on local variables.
To keep things simple, no body
nor invariants
blocks are required,
you can pass invariants mixed on the postconditions
but is optional,
it can be used on JavaScript, interpreted NimScript, runtime and compile-time.
Your CI service can compile and run a binary with all assertions enabled.
Contra adds 0
lines of code to Release build and 9
lines of code to Debug build.
Gulag Driven Development
Make your users work for you
You can now delegate the bug hunting on collaborators and trusted users,
encouraging them to try development binaries compiled with all assertions enabled.
But the same code can also produce a binary with all assertions disabled,
Contra produces no code when build for release, then you pwn on all Benchmarks.
Nim has automatic Dead Code Elimination.
- Convert your users into being your Gulag CI!.
Install
nimble install contra
Assuming you already have Nim installed.
Try Nim now, you are missing a ton of cool stuff.
Thank you for playing
- Star Contra on GitHub!
- Learn Nim today.
- Contra on Nimble.
- Nim Telegram Group
- Nim Sticker Pack.
- Grupo de Telegram en Español.
- Say Hi on the bridged Gitter, Matrix, Telegram, IRC, Discord.
👑
Top comments (3)
Nice post! I'm relatively new to Nim, hadn't found out about the side effects tracking yet.
Also you state that 'the contract shields our code from the bug' but don't mention what happens at compile-time or runtime in the example (i.e. what is the error message you will get).
Vanilla
assert
error, as the DbC uses in general.Maybe on the future it can self-document the contract using
CommentStmt
.This is great, but code becomes more cluttered and violates separation of concerns. Is there anyway to move pre- and post-conditions to the end of a file where all tests can be collected, then somehow inject the code into the functions, perhaps via macro? Think of it like decorators. For example:
And this would run exactly as if you had written the following:
I am new to Nim, so I don't know if the above syntax or similar is possible via a module import.
Cheers~