Introduction
- How to Begin Thinking Like a Programmer • Andy Harris shows how he gradually introduces programming concepts to beginners 📺
- Destroy All Software • Gary Bernhardt 📺
- Wat • Gary Bernhardt 📺
- Boundaries • Gary Bernhardt 📺
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 📖
- My programming beliefs as of July 2024 • High-level summary of what helps most when planning, coding and working with a team • Evan Hahn 📖
- Throw away your first draft of your code • Start the feature planning progress by spending a couple days building a throwaway prototype to uncover insights and unknowns that aren’t revealed by starting with design and ticket creation • Nicole Tietz 📖
Learning a new language
- How To Learn A New Programming Language • The Primeagen 📺
- Rust vs 7 Other Languages You Probably Haven’t Tried • Quick comparison of the same code in 8 lesser-known languages • Code to the Moon 📺
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)
- Graceful handling and recovery when that’s possible
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
- how my programming changed after 20 years • Shows an example of positive and negative correctness assertions that run at runtime and crash with a detailed error • ThePrimeTime 📺
- 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)
- Four colors for systems: network, disk, memory, CPU
- 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)
- e.g. assert on the minimum and maximum loop repetition/duration (
- 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
- 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
- Design phase:
- 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
- Premature Optimization • Code Aesthetic 📺
Computers
- Examples of floating point problems • Julia Evans 📖
Other tools
- JSON to YAML • Convert JSON to YAML 🛠️
Learning a new language
- Advent of Code - much-loved worldwide timed daily programming puzzle competition that happens every December
- teivah/advent-of-code: 🎄 My solutions to the Advents of Code, from 2015 to 2023 (450 🌟) • Teiva Harsanyi 🧑💻
- projects to help you learn a new language: 5 Great Python Project Ideas - Indently
Documentation
- Tied Up In Docs: A focus on decisions, rather than features or architecture, can make it easier to write and maintain documentation • Prefer ADRs because they don’t become stale, they capture helpful context about why the codebase changed, and they can be written at a high level • Luke Abel 📖
Inbox
-
Before you try to do something, make sure you can do nothing - The Old New Thing
-
JSON Web Tokens - jwt.io - handy online JWT decoder
-
what is nginx?
-
Q: what is a reverse proxy?
-
Timestamp Converter - useful UNIX/ISO timestamp syntax converter + UTC/your timezone converter
-
slack api: How to Message To DM Channel ID Using Slack API chatPostMessage - Stack Overflow
-
Asynchronous vs Multithreading and Multiprocessing Programming (The Main Difference) - Hussein Nasser
-
Use of Assertions – Embedded in Academia - John Regehr
-
Parsing data is nicer than only validating it – Matias Kinnunen - Matias Kinnunen
-
practical-tutorials/project-based-learning: Curated list of project-based tutorials - Includes Rust-specific list, but all lists have ideas for projects to build
-
Concurrency vs Parallelism • Concurrency is about dealing with multiple tasks at once; parallelism is about doing multiple tasks at once • TechPrep 📺
-
You Keep Using That Word • Sam Newman • GOTO 2024 • The word “asynchronous” means different things to different people; “non-blocking” is likely more helpful • GOTO Conferences 📺