Pragmatic minimalism as a software design tool
About me
- My name is Chris Down
- 5th most trusted user in the world on Unix & Linux Stack Exchange
- Sysadmin at Regent Markets Group
- Sysadmin/programmer at Initiatives of Change
- Tech lead at zenu.ca
- Minimalist
Considerations when designing software
- Is software the right way to fix this problem?
- What are the minimum requirements of this software?
- What areas of flexibility do there need to be to allow this software to adapt to change and improvement?
- What is the simplest correct way to achieve this software?
The simplest correct way
Not just about code, but tools.
- Language
- Libraries/modules
- Code structure
- Implementation decisions
- Design decisions
What is minimalism?
- Minimalism is not so much a process as it is a goal
- Things which may seem "simple" in the short term may not be in the long term
- Subjective depending on the person
- Removal/rejection of that which hinders achieving what you want
- Addition of that which makes it easier to achieve what you want
What is it that is wanted?
- Less code maintenance
- Fewer bugs
- More understandable code
- Better understanding of interlinking parts
Minimalism does not mean the removal of useful functionality
- If a constituent part of something is not useful, then the very act of removing it often improves other parts implicitly
- In determination of what is not useful, with modern version control systems, it is much less costly to liberally remove currently not useful code, with the knowledge that if you want it back later, you can do so
- Reduction in maintenance cost
Minimalism does not mean the removal of dependencies
- Support from others often helps you to get where you need by working in concert with their ideas
- In code, this manifests as willingness to use and give back to libraries and modules that are maintained outside of your control
- Minimalism does not mean the removal of dependencies, but such is often a useful side effect of its implementation
- Do not reinvent the wheel!
Usage: my_program tcp <host> <port> [--timeout=<seconds>]
my_program serial <port> [--baud=9600] [--timeout=<seconds>]
my_program (-h | --help | --version)
Raw
"""
Usage: my_program tcp <host> <port>[--timeout=<seconds>]
my_program serial <port> [--baud=9600] [--timeout=<seconds>]
my_program (-h | --help | --version)
"""
import sys
if len(sys.argv) >= 4 and sys.argv[1] == "tcp":
mode = "tcp"
host = sys.argv[2]
port = sys.argv[3]
if len(sys.argv) == 5 and sys.argv[4].startswith("--timeout")
[...]
optparse
"""
Usage: my_program tcp <host> <port>[--timeout=<seconds>]
my_program serial <port> [--baud=9600] [--timeout=<seconds>]
my_program (-h | --help | --version)
"""
import sys
import optparse
parser = optparse.OptionParser()
parser.add_option("--timeout")
parser.add_option("--baud")
options, args = parser.parse_args()
if not args:
sys.exit(1)
command = args[0]
if command == "tcp"
if len(args) != 3 or options.baud:
sys.exit(2)
host = args[1]
port = args[2]
elif command == "serial"
if len(args) != 2:
sys.exit(3)
host = None
port = args[1]
else:
sys.exit(4)
argparse
"""
Usage: my_program tcp <host> <port>[--timeout=<seconds>]
my_program serial <port> [--baud=9600] [--timeout=<seconds>]
my_program (-h | --help | --version)
"""
import argparse
parser = argparse.ArgumentParser()
subparsers = parser.add_subparsers()
parser_tcp = subparsers.add_parser("tcp")
parser_tcp.add_argument("<host>")
parser_tcp.add_argument("<port>")
parser_tcp.add_argument("--timeout", nargs="?")
parser_serial = subparsers.add_parser("serial")
parser_serial.add_argument("<port>")
parser_serial.add_argument("--baud", nargs=1)
parser_serial.add_argument("--timeout", nargs="?")
ArgumentError: argument --timeout: conflicting option string(s): --timeout
argparse
$ wc -l argparse.rst
1932
$ wc -l argparse.py
2373
Usage: my_program tcp <host> <port> [--timeout=<seconds>]
my_program serial <port> [--baud=9600] [--timeout=<seconds>]
my_program (-h | --help | --version)
WA LAU WEH, IT'S POSIX!!!
What is the proper way to represent this usage message in code?
Usage: my_program tcp <host> <port> [--timeout=<seconds>]
my_program serial <port> [--baud=9600] [--timeout=<seconds>]
my_program (-h | --help | --version)
docopt
"""
Usage: my_program tcp <host> <port> [--timeout=<seconds>]
my_program serial <port> [--baud=9600] [--timeout=<seconds>]
my_program (-h | --help | --version)
"""
from docopt import docopt
docopt(__doc__)
- 350 lines of code
- No dependencies
- Easily memorable syntax (superset of POSIX)
Making sensible design decisions
- It can't do X, but it can do Y
- Design decisions solely supporting little used functionality are (generally) not worthwhile
- Overengineering is one of the worst things you can do
- Underengineering is one of the worst things you can do
Commonly missed complexity issues
- Code complexity does not scale linearly with lines of code
- Each new piece of code has to interact with the entire codebase
- Not all complexity issues are in your codebase
- Some libraries try to do too much, and may limit your ability to use them effectively
- Some libraries actively slow down development due to their poor design
Summary
- Think about the language you are using
- Design in a way that allows flexibility
- Design in an easily comprehensible way
- Design in a correct way
- Design in a simple, minimalist way
Thank you!
chrisdown.name
chris@chrisdown.name
github.com/cdown