Skip to main content

Programming

Introduction

Figuring out a solution

  • An Intro to Spikes • “I use spikes, periods of code-changing activity that end with no pushes, all the time, at small scale and large, as a way to develop my path” • GeePaw Hill 📖
  • Short: WiP • Practical tips on applying divergent and convergent modes of thinking while coding and writing git commits • Rafał Pastuszak 📖

Learning a new language

Dependency Wrapping

Error handling

  • What to do when invalid inputs to a function are encountered?
    • Graceful handling and recovery when that’s possible
      • Generally, this should be possible in response to invalid user inputs
      • But, often won’t be possible if the bad state/data indicates a bug
    • Shutdown with detailed logging of state that led to crash when recovery is not possible
      • See Tiger Style (which favors runtime assertions + shutdowns when bugs are encountered)

Exception catching and recovery

  • Contrary to Tiger Style, etc which favors run-time assertions that crash a pod when an invalid state is encountered (often with the assumption that crashing one pod won’t result in any general loss of uptime since the system is distributed and other pods are still up and in a valid state), many favour anticipating failure modes and treating them as different success states
  • Possibly, shutting down is still the right thing to do, but it would be done gently rather than with a big crash
  • There are big upsides to this if the system is not distributed and crashing results in noticeable downtime for users
  • Other potential upsides include the ability to take the time to consciously log/save/send whatever debugging info would be handy
    • In contrast, it may sometimes be the case that debugging info is lost by doing this compared to crashing hardV
  • Exceptional Exception Handling • Google Testing Blog 📖

Run-time assertions

  • Example of helper functions for validating that the inputs to a function are valid (before proceeding with the function); they aim to reduce the boilerplate produced by writing custom guards and raising custom exceptions for each argument by making each check a one-liner
class FailedCheckException(Exception):
    """ Exception with message indicating check-failure location and values. """
    def __init__(self, message):
        self.message = message
        return
 
    def __str__(self):
        return self.message
 
 
def check_failed(message):
    """
    Log informative message and a stack trace to failed check condition.
        
    Raises: FailedCheckException
    """
    raise FailedCheckException(message)
 
 
def check(condition, message=None):
    """ Raise exception with message if condition is False. """
    if not condition:
        if message is None:
            message = "Check failed."
        check_failed(message)
 
 
def check_eq(obj1, obj2, message=None):
    """ Raise exception with message if obj1 != obj2. """
    if obj1 != obj2:
        if message is None:
            message = "Check failed: %s != %s" % (str(obj1), str(obj2))
        check_failed(message)
 
 
def check_ne(obj1, obj2, message=None):
    """ Raise exception with message if obj1 == obj2. """
    if obj1 == obj2:
        if message is None:
            message = "Check failed: %s == %s" % (str(obj1), str(obj2))
        check_failed(message)
 
 
def check_le(obj1, obj2, message=None):
    """ Raise exception with message if not (obj1 <= obj2). """
    if obj1 > obj2:
        if message is None:
            message = "Check failed: %s > %s" % (str(obj1), str(obj2))
        check_failed(message)
 
 
def check_ge(obj1, obj2, message=None):
    """ Raise exception with message if not (obj1 >= obj2). """
    if obj1 < obj2:
        if message is None:
            message = "Check failed: %s < %s" % (str(obj1), str(obj2))
        check_failed(message)
 
 
def check_lt(obj1, obj2, message=None):
    """ Raise exception with message if not (obj1 < obj2). """
    if obj1 >= obj2:
        if message is None:
            message = "Check failed: %s >= %s" % (str(obj1), str(obj2))
        check_failed(message)
 
 
def check_gt(obj1, obj2, message=None):
    """ Raise exception with message if not (obj1 > obj2). """
    if obj1 <= obj2:
        if message is None:
            message = "Check failed: %s <= %s" % (str(obj1), str(obj2))
        check_failed(message)
 
 
def check_notnone(obj, message=None):
    """ Raise exception with message if obj is None. """
    if obj is None:
        if message is None:
            message = "Check failed. Object is None."
        check_failed(message)
 
 
def check_numeric(obj, message=None):
    """ 
    Raise exception if not a form of numeric representation.
    
    NOTE:
    We've tried several implementations of this code, current one is the fastest
    we tried:
    1. isinstance(obj, numbers.Number)
        takes about 700 ns to run
    2. if type(obj) in [int, float, np.float32, ...]:
        takes about 300 ns to run
    3. current implementation takes about 100 ns to run
    """
    try:
        # multipyling by 1 would not work since for example "a" * 1 = "a"
        obj * 1.1
    except:
        if message is None:
            message = "Check failed. Object %s is not numeric." % (obj)
        check_failed(message)
 
 
def check_type(obj, obj_type, message=None):
    """ Raise exception if obj is not an instance of type. """
    if not isinstance(obj, obj_type):
        if message is None:
            message = "Check failed. Object is of type %s, expected %s." % (
                str(type(obj)),
                str(obj_type),
            )
        check_failed(message)
 
 
def check_in(item, iterable, message=None):
    """ Raise exception if obj is not in obj_list. """
    if item not in iterable:
        if message is None:
            message = "Check failed. {} is not in {}".format(item, iterable)
        check_failed(message)

Tiger Style

  • Tiger Style • TigerBeetle 📖
    • Design phase:
      • Four colors for systems: network, disk, memory, CPU
        • Each has limits
        • Each has failure modes
      • Two textures for systems: latency, bandwidth
      • Sketch as many back-of-the-envelope implementation options as possible to find the best approach to those colors and textures
      • Come up with great names for each component (to solidify the mental model and reduce future miscommunication)
      • Define fault model for each color (e.g. how to handle a memory allocation error?)
      • Try to keep components off disk (stateless is much easier than stateful if you can avoid state)
      • Reduce surface area of each component to make them as simple as possible (3 components are easier to remember than 200)
    • Coding:
      • Use assertions at runtime for all arguments and how they relate to each other and the state of the program, as well as for all return values
        • Every function should be checking what comes in and what goes out
        • Trip wires should shut down the program at runtime if they fail
        • The environment of a distributed system will surprise you, so you should also assert on what you do not expect
          • e.g. assert on the minimum and maximum loop repetition/duration (while true probably shouldn’t run forever)
          • learn the limits of your system and assert them so you understand the boundaries of each component (memory allocations, for loop iterations, etc)
        • Assertions are there to detect bad code (bugs) which can’t be predicted or gracefully handled; the safest thing to do is to shut down safely
        • In contrast, operational errors can be predicted and handled without shutting down (e.g. if low on memory, keep running but stop accepting new network connections)
        • Assertions can work well in production without harming high availability by crashing the pod that reached a bad state and restarting it in a good state, while other pods that haven’t hit that bug are still running
        • Over time, those bugs that cause crashes will become rarer and rarer, and they will flush out the problems in your code for you
        • Q: can this work with frontend apps without users noticing a problem?
        • In addition to the safety they add, assertions document invariants
          • You show other programmers both the positive space (your code) and the negative space (what shouldn’t be true)
          • That helps accelerate your team by helping them to understand your program even better
      • Don’t duplicate or alias variables (to avoid state getting out of sync)
      • Simplify function signatures and return types (shrink the scope)
      • Zero technical debt allowed (write the best version the first time, even if it means starting over)
      • Zero dependencies allowed (other than the Zig toolchain) for safety
  • The FASTEST and SAFEST Database • Mind-blowing presentation • Joran Dirk Greef & ThePrimeagen 📺
  • TigerStyle! (Or How To Design Safer Systems in Less Time) • Joran Dirk Greef 📺
    • Tiger Style! • Notes on the video above • Dave Gauer 📺
  • This Is A Game Changer - Negative space programming (define what your programming should not do as well as what it should do); focus on invariants (requirements that must be true to continue); in this example, assert an argument is a certain type; intentially crash the program loudly with the state that lead up to the crash so you can fix the issue based on the program state that led to it; like a guard clause, but instead of just exiting a function early, crashing the whole program • The Primeagen 📺
    • Reminds me of Tiger Style programming (used by the creators of tigerbeetle who built a new finance-oriented database with Zig)
    • My colleagues seemed to have the opposed idea that “exceptions should be exceptional”, meaning crashes should be avoided in favor of graceful termination; I think they meant you can/should still explicitly valid all invariants, but just opt to exit cleanly instead of assert + crashing
    • Which do I prefer?
    • Design by contract - Related software design approach (define contracts and assert they are true) • Wikipedia 📖

Optimization

Computers

Other tools

Inbox