Imagine two python scripts:
a.py:
def _2(n): return n*2
b.py
import a
print(a._2(2))
» 4
Entropy can said to be low because:
The result is predictable and consistent.
Redundant information low.
The number of symbols is low (while alternatives exist, like ‘print((lambda x: x ** 2)(2))’ we will build on this example)
We provided an input value (2) and received an output value (4), we have reasonable certainty that a.py executed to completion and return the correct result. Or do we?
Do either a.py or b.py know if a.py executed without error?
a.py should indicate to b.py that it executed without error:
def _2(n): return 1, n*2
which b.py can check:
import a
x = a._2(2)
if x[0] == 1: print(x[1])
Can we say reliabily that a.py executed without error? Not from the perspective of b.py, it has to assume that returned status of ‘1’ was set correctly. Therefore, a.py should really check there was no errors:
def _2(n):
try: return 1, n*2
except: return 0, None
Is b.py sure that a.py has checked for errors? No, so b.py also checks for errors:
import a
x = None
try:
x = a._2(2)
if x[0] == 1: print(x[1])
else: print("x not calculated")
except:
print("x not calculated")
Can the next programmer who comes along understand what is going on by looking at the module names and avaible names? This is uncertain, so we will change the naming to something more human language like.
a.py ==> square.py
def Square(n):
try: return True, n*2
except: return False, None
b.py ==> calculator.py
import square
result = None
try:
result = square.Square(2)
if (result[0]): print(result[1])
else: print("result not calculated")
except:
print("x not calculated")
Does the programmer or type checkers know what types are involved?
square.py
def Square(n: int) -> int:
try: return True, n*2
except: return False, None
calculator.py
import square
result: tuple[int, int] = (0, 0)
try:
result = square.Square(2)
if (result[0]): print(result[1])
else: print("result not calculated")
except:
print("result not calculated")
Does writing ‘Square(n: int)’ really tell the new programmer what ‘n’ is for? No.
square.py
def Square(n: "n is a number used as input to the function") -> int:
try: return True, n*2
except: return False, None
Does the code describe to the new programmer what the overall function is actually for? No.
square.py
def Square(n: "n is a number used as input to the function") -> int:
"""
Multiply the input number by 2.
This function takes a number as input and returns its double.
Args:
n (int or float): The number to be multiplied by 2.
Returns:
int or float: The result of multiplying the input by 2.
Examples:
>>> _2(3)
6
>>> _2(4.5)
9.0
"""
try: return True, n*2
except: return False, None
calculator.py
import square
"""
This script demonstrates the use of the square.Square function.
It attempts to calculate the square of 2 and prints the result or an error message.
Requires:
square module with a Square function that returns a tuple (bool, int).
Returns:
None. Outputs are printed to the console.
"""
# Initialize result tuple
result: tuple[int, int] = (0, 0)
try:
# Attempt to calculate the square of 2
result = square.Square(2)
# Check if calculation was successful and print appropriate message
if result[0]:
print(result[1])
else:
print("result not calculated")
except:
# Handle any exceptions that occur during the calculation
print("result not calculated")
When an error occurs, do we know why it occurred?
square.py
def Square(n: "n is a number used as input to the function") -> int:
"""
Multiply the input number by 2.
This function takes a number as input and returns its double.
Args:
n (int or float): The number to be multiplied by 2.
Returns:
int or float: The result of multiplying the input by 2.
Examples:
>>> _2(3)
6
>>> _2(4.5)
9.0
"""
try: return 200, n*2
except: return 500, None
calculator.py
import square
"""
This script demonstrates the use of the square.Square function.
It attempts to calculate the square of 2 and prints the result or an error message.
Requires:
square module with a Square function that returns a tuple (bool, int).
Returns:
None. Outputs are printed to the console.
"""
# Initialize result tuple
result: tuple[int, int] = (0, 0)
try:
# Attempt to calculate the square of 2
result = square.Square(2)
# Check if calculation was successful and print appropriate message
match result[0]:
case 200: print(result[1])
case 400: print("response: bad request")
case 401: print("response: not authorized")
case 403: print("response: forbidden")
case 500: print("response: internal error")
case _: print("response: not recognized")
except:
# Handle any exceptions that occur during the calculation
print("result not calculated")
As we do not know what to do with these errors at run time, we better log them so someone can look at them later when an error occurs.
calculator.py
import square
import logging
"""
This script demonstrates the use of the square.Square function.
It attempts to calculate the square of 2 and prints the result or an error message.
Requires:
square module with a Square function that returns a tuple (bool, int).
Returns:
None. Outputs are printed to the console.
"""
# Configure the logging system
logging.basicConfig(level=logging.INFO)
# Create a logger
logger = logging.getLogger(__name__)
# Initialize result tuple
result: tuple[int, int] = (0, 0)
try:
# Attempt to calculate the square of 2
result = square.Square(2)
# Check if calculation was successful and print appropriate message
match result[0]:
case 200: print(result[1])
case 400: logger.error("response: bad request")
case 401: logger.error("response: not authorized")
case 403: logger.error("response: forbidden")
case 500: logger.error("response: internal error")
case _: logger.error("response: not recognized")
except:
# Handle any exceptions that occur during the calculation
logger.error("result not calculated")
The new programmer needed to use another library that also had a Square() function, so better level naming to disambiguate the two.
calculator.py
from square import Square as mySquare
import mathematics
import logging
"""
This script demonstrates the use of the square.Square function.
It attempts to calculate the square of 2 and prints the result or an error message.
Requires:
square module with a Square function that returns a tuple (bool, int).
Returns:
None. Outputs are printed to the console.
"""
# Configure the logging system
logging.basicConfig(level=logging.INFO)
# Create a logger
logger = logging.getLogger(__name__)
# Initialize result tuple
result: tuple[int, int] = (0, 0)
try:
# Attempt to calculate the square of 2
result = square.mySquare(2)
# Check if calculation was successful and print appropriate message
match result[0]:
case 200: print(result[1])
case 400: logger.error("response: bad request")
case 401: logger.error("response: not authorized")
case 403: logger.error("response: forbidden")
case 500: logger.error("response: internal error")
case _: logger.error("response: not recognized")
except:
# Handle any exceptions that occur during the calculation
logger.error("result not calculated")
Some time later, many more modules and functions were added, so performance measuring and tracing information was also added, so the path of the invocations and their results were made clear to the person debugging problems when they occurred at 3 AM in the morning after referencing additional information on whether this error was occurring in a critical function/service or not.
In a much later code review, the question was asked “How much of this text and code is essential information for the Python interpreter, the software test team, the person who originally wrote the code, people who were later tasked with maintaining the code, and teams responsible for addressing runtime problems”.
Over a beer later that night two software development managers discussed software complexity. One asked the other “Why does software code become so complex?” The second software manager replied “Because information reduces uncertainty, and as you ask more questions, you need more information to reduce that uncertainty.” To which the first software manager replied, “I hate programmers who ask questions. I either don’t have the information they are asking for, in which case I attack them or rant until they lose interest or are shamed into going away. Or I simply can’t be bothered providing all the information they think they need. If I tell them 2 * 2 = 4, they should just accept it and go away.”
No sooner had the manager finished talking, and the head of security walked into the bar with the heads of compliance and risk management. The two software managers asked the others to join them. They all had many more beers that night.