Friday, June 1, 2018

Good Defaults for Technical Decisions

In my experience as a software engineer I've found a few "rules of thumb" for technical decisions. None of these are hard requirements or things that can never be false. However these are good guidelines if you don't have a reason to make a different decision. Unlike most engineering decisions which first present the constraints and then try and find a solution within them, this attempts to document decisions one should make if you didn't have any constraints in the first place.
Its possible you'll disagree with me on some of these and I'd like to understand why. That said, I'm not interested in specific projects where these are a bad idea but for an understanding about why these shouldn't be the default.
  1. Be explicit about your requirements. Don't automatically detect features, dependencies, or environment related issues. It is easier to change this later to make things more "magical" than go back and figure out what you need.
  2. Namespaces are good: even if you think you'll only ever need one. Its easier to modify in the future, versioning, etc.
  3. Errors ought to error. Warning ought to error or not exist. It is generally unhelpful to have noise in your output that you do nothing with. If a warning isn't meaningful, disable it.
  4. Keep scope local and private. Prefer hiding functions and information from the outside unless you have to decided to make this an API.
  5. Naming your first version as a "v1" and label it as such. During rewrites, migrations, or related issues prefer versions rather than names such as "next" or "new". There will likely be many "nexts" and only one v2.
  6. Structured is better than unstructured. Similar to the point about explicitness: it is easier to go from structured to unstructured than vice versa.
  7. Fixed is better than editable. Don't let people change things unless there is a reason to. This also applies to code (immutable is better than mutable).
  8. Don't rely on people not making mistakes. Even if you have perfect people, they might be tired, have something in their eye, misremember a fact, or otherwise be operating at a sub-optimal state.
  9. Name same things the same and different things differently. Use, and accept, the same formats for the same thing at all layers of the stack. As a counter example ruby outputs missing gems as name-version but gem(1) expects name:version.
This is a work in progress document and I'll try and update it over time

1 comment:

  1. I largely agree with your philosophies here, so it's only details that I'm interested in exploring.
    "Warnings ought to error or not exist." I'm not aware of any standardized distinctions between error, warning and notification, etc but I think there is a role for a warning, which is when an exceptional, unexpected or questionable conditioned has occurred, but it doesn't stop the process from completing, yet it deserves attention. Like, a car warns you that your oil is low, but you can still drive to the mechanic. Low battery warning. Maybe a cache got deleted and so data had to be recalculated: warn the user that it will take longer than usual but it's not blocked. Obviously, by comparison, an error is when things cannot continue.
    I treat all compiler warnings as errors, but that's personal preference. :)
    I agree that noise is very unhelpful, and so all warnings and notifications must be chosen carefully.

    "Function and information hiding." I know this is a very common and important tenet, but I do wonder if it is misphrased and the real intention is to hide implementation? Since information is the currency of our industry, it's the thing of value, hiding it by default seems counterintuitive. The information that we don't value today might be extremely valuable tomorrow, or it's actually already valuable today, but to someone else. I guess in effect I think we should hide implementation, but information and functions (interfaces) should be as public as possible by default. So many times I've encountered an interface where the really useful thing that I need is hidden away privately, requiring rework to make it public. If we're not sure about the design of an interface, then it's fine to keep it private until we are. But I think keeping things private by default diminishes the value of what we create by limiting its accessibility.
    Sometimes, the way to hide implementation is actually the specification/documentation, which can be a bit unsatisfying. For example you might document that a function returns a type that satisfies certain concepts (to use C++ parlance), but the exact type is unspecified and open to change. In practice the user can see exactly what type it is (the implementation), but the specification says you can't depend on it. (Much easier since C++11 introduced the auto variable type feature.)

    Otherwise, I completely agree with you! :)

    Here's a topic for you: when designing a solution to a problem, there is a tradeoff between specificity and generality. A specific solution helps people understand what the real, original problem was at the time, since that knowledge is often lost. A more general solution obscures what the original problem was, but of course has the benefit of solving a wider variety of problems. What is the good default?

    ReplyDelete

Have something you want to say? You think I'm wrong? Found something I said useful?
Leave a comment!