Skip to content

Instantly share code, notes, and snippets.

@dc0d
Last active March 15, 2023 20:13
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save dc0d/b6cb202740500a674928826432a8b8cf to your computer and use it in GitHub Desktop.
Save dc0d/b6cb202740500a674928826432a8b8cf to your computer and use it in GitHub Desktop.
Crap Code Properly

Crap Code Properly

This post contains some personal experiences formed while working on some personal projects. Do not use any of these in your work or your teams ¯\_(ツ)_/¯.

Directory Layout

Use this directory layout:

.
├── core
│   ├── actions
│   │   ├── roll-dice.go
│   │   └── roll-event.go
│   └── model
│   	├── dice.go
│   	└── event-oracle.go
└── delivery
	├── bot
	│   └── main.go
	├── grpc
	│   └── main.go
	└── web
    	    └── main.go

This is a simple program. It performs two actions: rolling dice and choosing a random event. Even after some years, when you return to this project, you will immediately know what it does. This specific project is written in Go (golang). I have reimplemented the same thing in TypeScript. And recently, I have reimplemented it partially in Rust. In TypeScript and Rust case, this directory layout sits under the src directory. The program provides much more functionality. But the size of the codebase is not important. What is important is the first level of communication in a codebase is the directory layout. The codebase is not a proper pile of crap code if it is absent. It is a crappy pile of crap code.

If our program is a bit bigger and has multiple entities, we group things under directories named after those entities:

.
├── core
│   ├── actions
│   │   ├── character
│   │   │   ├── create-npc.go
│   │   │   ├── generate-motive.go
│   │   │   └── generate-name.go
│   │   └── oracle-table
│   │   	├── roll-event.go
│   │   	└── roll-location.go
│   └── model
│   	├── character
│   	│   ├── character.go
│   	│   └── service.go
│   	├── dice.go
│   	└── oracle-table
│       	└── service.go
└── delivery
	├── bot
	│   └── main.go
	├── grpc
	│   └── main.go
	└── web
    	    └── main.go

You get the idea. This directory layout makes it easier to onboard to an old project and pick up everything quickly. Directory Layout is the first step/level of communication, not the README.

Code Dependency Constraints

  • The code for model can not depend on other parts.
  • The code for actions can depend only on model.
  • The code for delivery can depend on both the code for actions and the code for model.

What is an action?

An action is an interface with this signature:

type Action[CTX context.Context, IN, OUT any, ERROR error] interface {
    Do(ctx CTX, in IN) (result OUT, err ERROR)
}

It receives an execution context and the input. It returns an output or an error. In the Go version, the context is of type context.Context. In the TypeScript version, it is just a number representing the timeout. In the TypeScript version, instead of returning an error, it throws an exception.

Stuff before the action does not leak into stuff below the action.

TODO:

  • depend on abstractions but inject the real thing while testing.
  • do not run tests in docker containers.
  • if a dependency (like a database) is fast to respond, run it locally in a container, have it up during development and run your tests against it. as long as it is a dependency which is fast to respond and you can have it locally, do it.
  • if a dependency is very slow to respond, or it is not possible to have it locally, mock it in the tests or use a fake. there are some tools that help creating fake versions of a HTTP API - for example.
  • write unit tests first when you understand what you want to do, but you want to discover the best (meh) way to do it.
  • if you don't understand the problem and still want to write code ... be careful and patient; rinse and repeat.
  • log-based/signal-based testing/verification.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment