A tool for design-by-contract in Go

gocontracts

build status Coverage Status Go Report Card godoc

gocontracts is a tool for design-by-contract in Go.

It generates pre- and post-condition checks from the function descriptions so that the contracts are included in the documentation and automatically reflected in the code.

(If you need invariants, please let us know by commenting on this issue: https://github.com/Parquery/gocontracts/issues/25.)

Workflow

You invoke gocontracts on an individual Go file. Gocontracts will parse the file and examine the descriptions of all the functions for contracts. The existing contract checks will be overwritten to match the contracts in the description.

A usual workflow includes defining the contracts in the function description and invoking gocontracts to automatically update them in the code.

Each contract is defined as a one-line item in a bulleted list. Gocontracts does not validate the correctness of the conditions (e.g. undefined variables, syntax errors etc.). The code is simply inserted as-is in the header of the function body.

Since contracts are logical conditions of the function, failing a contract causes a panic(). If you need to validate the input, rather than check logical pre- and post-conditions of the function, return an error and do not abuse the contracts.

Related Projects

At the time of this writing (2018-08-19), we found only a library that implemented design-by-contract as functions (https://github.com/lpabon/godbc) and a draft implementation based on decorators (https://github.com/ligurio/go-contracts).

None of them allowed us to synchronize the documentation with the code.

Examples

Simple Example

Given a function with contracts defined in the description, but no checks previously written in code:

package somepackage

// SomeFunc does something.
//
// SomeFunc requires:
//  * x >= 0
//  * x < 100
//
// SomeFunc ensures:
//  * !strings.HasSuffix(result, "smth")
func SomeFunc(x int) (result string) {
	// ...
}

, gocontracts will generate the following code:

package somepackage

// SomeFunc does something.
//
// SomeFunc requires:
//  * x >= 0
//  * x < 100
//
// SomeFunc ensures:
//  * !strings.HasSuffix(result, "smth")
func SomeFunc(x int) (result string) {
	// Pre-conditions
	switch {
	case !(x >= 0):
		panic("Violated: x >= 0")
	case !(x < 100):
		panic("Violated: x < 100")
	default:
		// Pass
	}

	// Post-condition
	defer func() {
		if strings.HasSuffix(result, "smth") {
			panic("Violated: !strings.HasSuffix(result, \"smth\")")
		}
	}()

	// ...
}

Note that you have to manually import the strings package since goconracts is not smart enough to do that for you.

Additionally, if you want to use go doc, you have to indent conditions with a space in the function description so that go doc renders them correctly as bullet points.

Condition Labels

Certain conditions can be hard to understand when the formal definition lacks a textual description. Gocontracts therefore allows you to introduce condition labels to clarify the intent:

package somepkg

// SomeFunc does something.
//
// SomeFunc requires:
//  * positive: x > 0
//  * not too large: x < 100
func SomeFunc(x int, y int) (result string, err error) {
	// Pre-conditions
	switch {
	case !(x > 0):
		panic("Violated: positive: x > 0")
	case !(x < 100):
		panic("Violated: not too large: x < 100")
	default:
		// Pass
	}
	
	// ...
}

Since we need to distinguish the condition labels from the condition code, we had to restrict the labels to strings of characters [a-zA-Z0-9_;.\-=' ]. Otherwise, if we allowed a full character set, there would be ambiguities between the label and the code.

(We decided against clutter in the documentation such as Go string literals. It is our hope that restricted character set should suit 99% use cases out there. Please let us know if this is not the case. See also https://github.com/golang/go/issues/16666 .)

Condition Initialization

Go allows you to initialize a condition and execute a simple statement before the check. For example, the initialization is common when checking if an item belongs to a map:

if _, ok := someMap[3]; ok {
	...
}

Following Go, Gocontracts also allows you to include the initialization in the condition. The following code snippet shows you how to document the initialization and what Gocontracts generates:

// SomeFunc does something.
//
// SomeFunc requires:
//  * _, ok := someMap[3]; ok
func SomeFunc(someMap map[string]bool) {
	// Pre-condition
	if _, ok := someMap[3]; !ok {
		panic("Violated: _, ok := someMap[3]; ok")
	}

	// ...
}

Conditioning on a Variable

Usually, functions either return an error if something went wrong or valid results otherwise. To ensure the contracts conditioned on the error, use implication and write:

// Range returns a range of the timestamps available in the database.
//
// Range ensures:
//  * err != nil || (empty || first < last)
func (t *Txn) Range() (first int64, last int64, empty bool, err error) {
	// ...
}

. Gocontracts will generate the following code:

// Range returns a range of the timestamps available in the database.
//
// Range ensures:
//  * err != nil || (empty || first <= last)
func (t *Txn) Range() (first int64, last int64, empty bool, err error) {
	// Post-condition
	defer func() {
	    if !(err != nil || (empty || first < last)) {
	    	panic("Violated: err != nil || (empty || first < last)")
	    }	
	}()
	
	// ...
}

Note that conditioning can be seen as logical implication (A ⇒ B can be written as ¬A ∨ B). In the above example, we chained multiple implications as err == nil ⇒ (¬ empty ⇒ first ≤ last).

State Transitions

When you want to formally define contracts on state transitions you need to capture the state before and after the function execution. However, Gocontract's conventional post-conditions allow you only to access the state afer the execution.

In order to capture the state before the execution, you need to write a preamble. The preamble is a code snippet written in your documentation and automatically synced with the function body by gocontracts. The snippet must follow the Godoc convetion and be indented with a whitespace (or a tab).

The preamble is executed just after the pre-condition(s) have been verified.

Here is a brief (and admittedly a bit constructed) example with the generated code already included:

package somepackage

// increaseFirst increases the first element of the array.
//
// increaseFirst requires:
//  * len(a) > 0
//
// increaseFirst preamble:
//  oldFirst := a[0]
//
// increaseFirst ensures:
//  * a[0] == oldFirst + 1
func increaseFirst(a []int) {
	// Pre-condition
	if !(len(a) > 0) {
		panic("Violated: len(a) > 0")
	}

	// Preamble starts.
	oldFirst := a[0]
	// Preamble ends.

	// Post-condition
	defer func() {
		if !(a[0] == oldFirst + 1) {
			panic("Violated: a[0] == oldFirst + 1")
		}
	}()

	// Implementation
	a[0]++
}

Toggling Contracts

When developing a library, it is important to give your users a possibility to toggle families of contracts so that they can adapt your contracts to their use case. For example, some contracts of your library should be verified in testing and in production, some should be verified only in testing of their modules and others should be verified only in your unit tests, but not in theirs.

To that end, you can use build tags to allow toggling of contracts at compile time (https://golang.org/pkg/go/build/#hdr-Build_Constraints). Define for each of the contract family a separate file which is built dependening on the build tag. In each of these files, define constant booleans (e.g., InTest, InUTest). Depending on the scenario, set these variables appropriately: in production scenario, InTest = false and InUTest = false, in the test scenario for others InTest = true and InUTest = false, while in the scenario of your unit tests InTest = true and InUTest = true.

The examples of the three files follow.

contracts_prod.go:

// +build prod,!test,!utest

package somepackage

const InTest = false
const InUTest = false

contracts_testing.go:

// +build !prod,test,!utest

package somepackage

const InTest = true
const InUTest = false

contracts_utest.go:

// +build !prod,!test,utest

package somepackage

const InTest = true
const InUTest = true

Include each of these boolean constants in your contract conditions and && with the condition. For example, this is how you extend the postcondition of the function Range written in the previous section to be verified only in yours and theirs tests, but not in production:

// Range returns a range of the timestamps available in the database.
//
// Range ensures:
//  * !InTest || (err != nil || (empty || first <= last))
func (t *Txn) Range() (first int64, last int64, empty bool, err error) {
	...
}

Since constant booleans are placed first in the conjunction, the rest of the condition will not be evaluated incurring thus no computational overhead in the production at runtime.

Usage

Gocontracts reads the Go file and outputs the modified source code to standard output:

gocontracts /path/to/some/file.go

You can modify the file in-place by supplying the -w argument:

gocontracts -w /path/to/some/file.go

If you want to remove the contract checks from the code, supply the -r argument:

gocontracts -w -r /path/to/some/file.go

The remove argument is particularly useful when you have a build system in place and you want to distinguish between the debug code and the release (production) code.

Before building the release code, run the gocontracts with -r to remove the checks from the code.

Installation

We provide x86 Linux binaries in the "Releases" section.

To compile from code, run:

go get -u github.com/Parquery/gocontracts

Development

  • Fork the repository to your user on Github.

  • Get the original repository:

     go get github.com/Parquery/gocontracts
  • Indicate that the local repository is a fork:

     cd gocontracts
     git remote add fork https://github.com/YOUR-GITHUB-USERNAME/gocontracts.git
  • Make your changes.

  • Push the changes from the local repository to your remote fork:

     git push fork
  • Create a pull request on Github and send it for review.

Versioning

We follow Semantic Versioning. The version X.Y.Z indicates:

  • X is the major version (backward-incompatible),
  • Y is the minor version (backward-compatible), and
  • Z is the patch version (backward-compatible bug fix).
Comments
  • Consider use of functions as well?

    Consider use of functions as well?

    Hi, I have been looking for DBC support for go as well. I like your approach of ensuring that the generated code agrees with the documentation. I wonder if we could have the best of both worlds if you generated code that used functions to implement the contract as per https://godoc.org/github.com/lpabon/godbc?

    Personally I want to assert pre-conditions in production but not post-conditions (which are often hard to compute and covered by extensive unit tests. I also want to document all my pre-conditions , post-condiions and invariants even those that are hard to compute. Coming from C++ I used doxygen for DBC commenting, an assertion macro for pre-conditions and unit tests for post-conditions and invariants. Generating a contract checking wrapper to a function would be very useful in the testing context.

    The ultimate DBC package to me, might allow:

    • just checking that pre-conditions, post-conditions and invariants in the code and documentation match
    • generating checks from documentation - as you do now
    • generating documentation from checks - the inverse - which I presume is not done
    • allowing documented checks which are not generated e.g. "foobar() must have been invoked first to setup the snafu"

    I would like to propose a minimum change towards this goal of:

    • allowing documented checks which are not generated
    • using functions for the generated checks (like https://godoc.org/github.com/lpabon/godbc) so that your package is still usable/preferable even if the code generation part is not used.

    I presume this package overwrites generated contracts rather than "checking that pre-conditions, post-conditions and invariants in the code and documentation match" which is a harder problem but please correct me if I am wrong.

    What is the current behaviour if code for a check cannot be generated? Is this left to be picked up as a build failure downstream?

    Regards,

    Bruce.

  • precommit script

    precommit script

    Hi, I notice the precommit script is written in python. I'm not sure that is a good fit with the go ecosystem. Couldn't you do the same directly in go?

    Regards,

    Bruce.

  • Process multiple files at once

    Process multiple files at once

    The tool currently supports only processing one file at a time. For example:

    ls hello.go hello_test.go gocontracts *.go Expected the path to the file as a single positional argument, but got positional 2 argument(s)

    This would make it less convenient to use in production.

  • Added blank line to build constraint examples

    Added blank line to build constraint examples

    As specified in https://golang.org/pkg/go/build/#hdr-Build_Constraints:

    "To distinguish build constraints from package documentation, a series of build constraints must be followed by a blank line."

  • Use go doc safe comment style

    Use go doc safe comment style

    I've been wrestling with godoc (I think I may drop it altogether as its far too simplistic). Anyway I note that:

    // SomeFunc does something.
    //
    // SomeFunc requires:
    //  * x >= 0
    //  * x < 100
    //
    // SomeFunc ensures:
    //  * !strings.HasSuffix(result, "smth")
    //
    

    Renders as:

    >go doc somePackage.SomeFunc
    func SomeFunc(x int) (result string)
         SomeFunc does something.
     
         SomeFunc requires:
     
         * x >= 0
         * x < 100
     
         SomeFunc ensures:
     
         * !strings.HasSuffix(result, "smth")
    

    which is nicer than the:

    
    func SomeFunc(x int) (result string)
        SomeFunc does something.
    
        SomeFunc requires: * x >= 0 * x < 100
    
        SomeFunc ensures: * !strings.HasSuffix(result, "smth")
    
    

    which you get without indenting the conditions and making them into a pre-formatted text block. This doesn't affect gocontracts code generation at all. Its purely a documentation issue to recommend users do this.

  • cannot build

    cannot build

    This is probably me being foolish but I cannot get this to build or run the tests. I am using go 1.7.4 on Debian 9, though I tried go 1.11 as well briefly. Any idea what I might be doing wrong?

    ~/work/git/go/src/gocontracts$ go build main.go
    main.go:6:2: cannot find package "github.com/Parquery/gocontracts/gocontracts" in any of:
    	/usr/lib/go-1.7/src/github.com/Parquery/gocontracts/gocontracts (from $GOROOT)
    	/home/brucea/work/git/go/src/github.com/Parquery/gocontracts/gocontracts (from $GOPATH)
    ~/work/git/go/src/gocontracts$ cd gocontracts/
    ~/work/git/go/src/gocontracts/gocontracts$ ls
    process.go  process_test.go  testcases
    brucea@debianvm708286:~/work/git/go/src/gocontracts/gocontracts$ go build
    brucea@debianvm708286:~/work/git/go/src/gocontracts/gocontracts$ go test -v
    # gocontracts/gocontracts
    process_test.go:11:2: cannot find package "github.com/Parquery/gocontracts/gocontracts/testcases" in any of:
    	/usr/lib/go-1.7/src/github.com/Parquery/gocontracts/gocontracts/testcases (from $GOROOT)
    	/home/brucea/work/git/go/src/github.com/Parquery/gocontracts/gocontracts/testcases (from $GOPATH)
    FAIL	gocontracts/gocontracts [setup failed]
    ~/work/git/go/src/gocontracts/gocontracts$ go test -v testcases/*.go
    ?   	command-line-arguments	[no test files]
    ~/work/git/go/src/gocontracts/gocontracts$ go test -v *.go
    # command-line-arguments
    process_test.go:11:2: cannot find package "github.com/Parquery/gocontracts/gocontracts/testcases" in any of:
    	/usr/lib/go-1.7/src/github.com/Parquery/gocontracts/gocontracts/testcases (from $GOROOT)
    	/home/brucea/work/git/go/src/github.com/Parquery/gocontracts/gocontracts/testcases (from $GOPATH)
    FAIL	command-line-arguments [setup failed]
    
    
    
    
  • Changed sign of boolean InTest in example

    Changed sign of boolean InTest in example

    If we want a condition C to be evaluated only when InTest=true, we ought to write: !InTest || C

    So that in production, the condition above is true as a consequence of InTest=false, with no need to check what follows.

  • Don't panic, return early

    Don't panic, return early

    Calling a panic is a no-no and will cause programs to blow up.

    Also, unfortunately these checks will slow down programs, but I personally think it's worth it.

  • Invariants

    Invariants

    The most interesting part of the design-by-contract, namely invariants, is still missing and I'd like to hear your feedback on how to implement it.

    In an OO language, invariants should hold after the constructor and before and after invocation of each public method. They are particularly interesting way to formalize the properties on your more complex contained data structures. For example, if you internally use a sorted slice to perform binary search on it for interval queries, an invariant would be that the slice is always sorted. Another example is a directed acyclic graph with an invariant that there should be no cycles.

    For a more detailed (and better) article on advantages of design-by-contract in general and invariants in particular, please see: https://cacm.acm.org/blogs/blog-cacm/227928-why-not-program-right/fulltext

    Right now, gocontracts operates on individual files. To add the invariants to a type, I thought about passing the package folder (instead of individual files) to gocontracts, inspecting the type comments and iterating through all the public methods associated with the type. Since there are no constructors in Go, I'd establish the invariants in all the public methods as additional pre and postconditions.

    I though that a documentation defining the invariants could look like this:

    // SomeType represents something.
    //
    // SomeType establishes:
    // * x >= 0
    // * x < Width
    // * x < somePrivateProperty
    type SomeType struct {
       // ...
    }
    

    For some method:

    // SomeFunc does something.
    //
    // SomeFunc requires:
    // * x % 3 == 0
    func (s *SomeType) SomeFunc(x int) {
       // ...
    }
    

    gocontracts would append the invariants of the type to the pre and postconditions and generate the checks as:

    // SomeFunc does something.
    //
    // SomeFunc requires:
    // * x % 3 == 0
    func (s *SomeType) SomeFunc(x int) {
       // Pre-conditions
       switch {
       case !(s.x >= 0):
          // panic ...
       case !(x % 3 == 0):
          // panic ...
       // ...
       }
    
       // Post-conditions
       defer func() {
          switch {
          case !(s.x >= 0):
             // panic ...
          // ...
          }
       }()
       // ...
    }
    

    How do you like the idea? Is the syntax appealing? Would you mind if you could only disable/enable checks on per-package basis?

    In case gocontracts would still keep on operating on individual files, would it be confusing if it parsed all the other go files in the file's directory to read the invariants and then apply it to only the given file (and not establishing invariants on the functions defined in other files)?

    Thanks a lot for your feedback!

Golang loose implementaion of Domain Driven Design ( DDD )

This template is a loose implementation of golang DDD (Domain Driven Design). Template already setup user register login and refresh session route. It

Dec 25, 2022
The forgotten go tool that executes and caches binaries included in go.mod files.
The forgotten go tool that executes and caches binaries included in go.mod files.

The forgotten go tool that executes and caches binaries included in go.mod files. This makes it easy to version cli tools in your projects such as gol

Sep 27, 2022
[TOOL, CLI] - Filter and examine Go type structures, interfaces and their transitive dependencies and relationships. Export structural types as TypeScript value object or bare type representations.

typex Examine Go types and their transitive dependencies. Export results as TypeScript value objects (or types) declaration. Installation go get -u gi

Dec 6, 2022
elPrep: a high-performance tool for analyzing sequence alignment/map files in sequencing pipelines.
elPrep: a high-performance tool for analyzing sequence alignment/map files in sequencing pipelines.

Overview elPrep is a high-performance tool for analyzing .sam/.bam files (up to and including variant calling) in sequencing pipelines. The key advant

Nov 2, 2022
A command line tool to generate sequence diagrams
A command line tool to generate sequence diagrams

goseq - text based sequence diagrams A small command line utility used to generate UML sequence diagrams from a text-base definition file. Inspired by

Dec 22, 2022
Wprecon, is a vulnerability recognition tool in CMS Wordpress, 100% developed in Go.
Wprecon, is a vulnerability recognition tool in CMS Wordpress, 100% developed in Go.

WPrecon (Wordpress Recon) Hello! Welcome. Wprecon (Wordpress Recon), is a vulnerability recognition tool in CMS Wordpress, 100% developed in Go. Featu

Dec 25, 2022
Stargather is fast GitHub repository stargazers information gathering tool

Stargather is fast GitHub repository stargazers information gathering tool that can scrapes: Organization, Location, Email, Twitter, Follow

Dec 12, 2022
Podman: A tool for managing OCI containers and pods

Podman: A tool for managing OCI containers and pods Podman (the POD MANager) is a tool for managing containers and images, volumes mounted into those

Jan 1, 2023
A tool that facilitates building OCI images
A tool that facilitates building OCI images

Buildah - a tool that facilitates building Open Container Initiative (OCI) container images The Buildah package provides a command line tool that can

Jan 3, 2023
A tool to run queries in defined frequency and expose the count as prometheus metrics.
A tool to run queries in defined frequency and expose the count as prometheus metrics.

A tool to run queries in defined frequency and expose the count as prometheus metrics. Supports MongoDB and SQL

Jul 1, 2022
CodePlayground is a playground tool for go and rust language.

CodePlayground CodePlayground is a playground tool for go and rust language. Installation Use homebrews to install code-playground. brew tap trendyol/

Mar 5, 2022
Squizit is a simple tool, that aim to help you get the grade you want, not the one you have learnt for.
Squizit is a simple tool, that aim to help you get the grade you want, not the one you have learnt for.

Squizit is a simple tool, that aim to help you get the grade you want, not the one you have learnt for. Screenshots First, input PIN Then enjoy! Hoste

Mar 11, 2022
A tool suite for Redis profiling

Insecticide Insecticide is a tool suite for Redis profiling. It finds ambiguous values in your redis configuration.

Dec 13, 2021
Go package for dealing with Mantis Bug Tracking tool

BlueMantis is a Go package in development that aim to make the process of sending issues and bugs in Go applications to the Open Source Bug Tracking software MantisBT.

Aug 3, 2021
Scaffolding tool for golang based services

Scaffolding tool for golang based services

Mar 6, 2022
a tool for getting metrics in containers

read metrics in container if environment is container, the cpu ,memory is relative to container, else the metrics is relative to host. juejing link :

Oct 13, 2022
An Elasticsearch Migration Tool.

An Elasticsearch Migration Tool Elasticsearch cross version data migration. Dec 3rd, 2020: [EN] Cross version Elasticsearch data migration with ESM Fe

Dec 19, 2022
Nomad Pack is a templating and packaging tool used with HashiCorp Nomad.

Nomad Pack is a templating and packaging tool used with HashiCorp Nomad.

Jan 4, 2023
Transaction management tool for taxable investments

Market Lot Robot Transaction management tool for taxable investments. How it works Run the web socket server with the following command: go run . Visi

Oct 19, 2021