Why this matters
Every policy in a system has a level — defined by its distance from inputs and outputs. Reading from a file is low-level: it's directly adjacent to an input device. Writing to a display is low-level: it's adjacent to an output device. An encryption algorithm, however, is high-level: it transforms data but doesn't care where data came from or where it will go. It is maximally distant from any particular I/O device.
This concept governs dependency direction. Low-level policies change often — file formats, API shapes, UI structures. High-level policies change rarely — business logic, algorithms, fundamental rules. If a low-level policy depends on a high-level one (correctly), a change to the file format never touches the encryption logic. If the dependency is reversed — if the high-level algorithm knows about the file — a trivial change to input format requires touching the most valuable code in the system.
The problem
An encrypt() function that simultaneously opens files, iterates characters, and applies the cipher — all levels mixed in one function, impossible to reuse the cipher logic alone.
Bad
import sys
def encrypt(input_path: str, output_path: str, shift: int) -> None:
# Low-level: file I/O (close to input/output devices)
with open(input_path, "r") as fin, open(output_path, "w") as fout:
for line in fin:
result = ""
for char in line:
# High-level: cipher rule (farthest from I/O)
if char.isalpha():
base = ord("A") if char.isupper() else ord("a")
result += chr((ord(char) - base + shift) % 26 + base)
else:
result += char
fout.write(result)
# The cipher algorithm is buried inside file-open and file-write.
# To reuse the cipher for HTTP, stdin, or sockets: copy-paste only.
# To test the cipher alone: must provide real files.
# All levels are at the same level — there is no level.
import { readFileSync, writeFileSync } from "fs";
export function encryptFile(inputPath: string, outputPath: string, shift: number): void {
const content = readFileSync(inputPath, "utf-8"); // low-level
const result = content.split("").map(char => { // mixed levels
if (/[a-z]/i.test(char)) {
const base = char >= "a" ? 97 : 65;
const offset = (char.charCodeAt(0) - base + shift) % 26;
return String.fromCharCode(base + offset); // high-level cipher rule
}
return char;
}).join("");
writeFileSync(outputPath, result, "utf-8"); // low-level
}
// The cipher is trapped inside file I/O. Not reusable. Not independently testable.
The solution
Pure cipher function at the highest level — knows nothing about I/O. File and stdin wrappers at lower levels depend on it, not the reverse.
Good
# High-level policy (farthest from I/O) — pure and reusable
def caesar_cipher(text: str, shift: int) -> str:
result = []
for char in text:
if char.isalpha():
base = ord("A") if char.isupper() else ord("a")
result.append(chr((ord(char) - base + shift) % 26 + base))
else:
result.append(char)
return "".join(result)
# Lower-level: file-based wiring (depends on caesar_cipher, not vice versa)
def encrypt_file(input_path: str, output_path: str, shift: int) -> None:
with open(input_path, "r") as fin, open(output_path, "w") as fout:
for line in fin:
fout.write(caesar_cipher(line, shift))
# Lower-level: stdin/stdout wiring (depends on the same high-level rule)
def encrypt_stdin(shift: int) -> None:
import sys
for line in sys.stdin:
sys.stdout.write(caesar_cipher(line, shift))
# Dependency direction: encrypt_file → caesar_cipher (low → high, correct)
# Test: assert caesar_cipher("Hello", 1) == "Ifmmp" — no file system needed.
// High-level policy — depends on nothing, changes only for business reasons
export function caesarCipher(text: string, shift: number): string {
return text.split("").map(char => {
if (/[a-z]/i.test(char)) {
const base = char >= "a" ? 97 : 65;
const offset = (char.charCodeAt(0) - base + shift) % 26;
return String.fromCharCode(base + offset);
}
return char;
}).join("");
}
// Lower-level: file-based wiring
import { readFileSync, writeFileSync } from "fs";
export function encryptFile(inputPath: string, outputPath: string, shift: number): void {
writeFileSync(outputPath, caesarCipher(readFileSync(inputPath, "utf-8"), shift), "utf-8");
}
// Lower-level: HTTP wiring (same high-level rule, different delivery mechanism)
import type { Request, Response } from "express";
export function encryptEndpoint(req: Request, res: Response): void {
const { text, shift } = req.body as { text: string; shift: number };
res.json({ encrypted: caesarCipher(text, shift) });
}
// caesarCipher depends on nothing.
// encryptFile and encryptEndpoint depend on caesarCipher.
// Dependencies flow from volatile (I/O) → stable (cipher rule).
// Test: expect(caesarCipher("Hello", 1)).toBe("Ifmmp");
Key takeaway
High-level policies change rarely and for business reasons. Low-level details change often and for technical reasons. Dependencies should flow from volatile to stable — from the I/O boundary inward toward the algorithm. Never the reverse.