People don't understand how to build the perfect abstraction. They think you can't but the truth is, you can get really really close.

What usually happens is even if you designed something that is 99% perfect what ends up happening is requirements change or you didn't anticipate some emergent complexity occurring out of nowhere. Due to this, the abstraction always ends up being imperfect.

But like I said you can actually get around this problem using a technique people usually talk negatively about.

The perfect abstraction is actually ravioli code.

Take for example: fizz buzz. Your client wants you to impliment fizz buzz.

Typical solution/abstraction level:

def fizzbuzz(n: int) -> None:
for i in range(n):
if i % 5 == 0:
print("Buzz")
elif i % 3 == 0:
print("Fizz")
else:
print(str(i))

Ravioli code:

def map(func, list_of_stuff):
return [func(i) for i in list_of_stuff]

def create_numbers(n: int) -> List[int]:
return [i for i in range(n)]

Enum FizzBuzzCase = Fizz | Buzz | Int
def logic(i: int) -> FizzBuzzCase:
if i % 5 == 0:
if i % 5 == 0:
return Buzz
elif i % 3 == 0:
return Fizz
else:
return Int

def handle_logic(logic_result: FizzBuzzCase, i: int) -> str:
if logic_result is Fizz:
return "Fizz"
elif logic_result is Buzz:
return "Buzz"
elif logic_result is Int:
return str(i)

def display_string(s: str) -> None:
print(s)

def fizzbuzz(n: int) -> None:
map(display_string(handle_logic(logic(i))), create_numbers(n))

Less readable more complicated. But believe it or not this ravioli code is a better fit for what most people perceive as the "perfect abstraction" Although both examples of code fit the requirements of the code perfectly as with all projects requirements change and the ravioli code is the only one that can maintain code that is always a perfect fit with the requirments.

Let's say the client wants to change this: Fizz must be printed when less than 50 and buzz must be printed when >= 50. Just rewrite one function:

Enum FizzBuzzCase = Fizz | Buzz | Int
def logic(i: int) -> FizzBuzzCase:
if i < 50:
return Fizz
else:
return Buzz

Let's say the client also wanted to reverse the numbers printing from n to 0. Just rewrite one function:

def create_numbers(n: int) -> List[int]:
return [i for i in range(n, 0, -1)]

Let's say the client also wanted to change from printing to logging to a file. Just rewrite one function:

def display_string(s: str) -> None:
log_to_file(s, "log.txt")

Let's say the client also wanted the system to print "Foo" instead of Fizz and "Boo" instead of Buzz. Just rewrite one function:

def handle_logic(logic_result: FizzBuzzCase, i: int) -> str:
if logic_result is Fizz:
return "Foo"
elif logic_result is Buzz:
return "Boo"
elif logic_result is Int:
return str(i)

None of these edits could be done with the initial version of FizzBuzz without a massive rewrite or ugly ass hacks

Essentially the prefect abstraction is a program with many many small abstractions. The smaller the abstractions the better. Those abstractions combine to form higher order abstractions and those higher order abstractions combine again to form even higher abstractions.

By building your abstractions using this method you can tune your abstraction at the smallest level of resolution to the point where it is nearly perfect.

If requirements change you simply need to retune the higher order abstraction by rearranging the lower level primitives.

Ravioli Code is the key to the perfect abstraction. But if you look at the ravioli code, initially, overall it looks pretty bad. It's verbose, harder to read and over complicated.

However the ravioli code fits the shape of the problem and evolves with changing requirements!

One caveat. Don't try doing this with OOP. It doesn't work. Try it if you don't believe me. First break fizz buzz into OOP ravioli primitives, then try to see if it's easily adaptable to the changes I listed above.