Skip to content

Instantly share code, notes, and snippets.

@nicholaswmin
Last active November 29, 2024 15:55
Show Gist options
  • Save nicholaswmin/714f7ca25e3da15024593663bae0dc33 to your computer and use it in GitHub Desktop.
Save nicholaswmin/714f7ca25e3da15024593663bae0dc33 to your computer and use it in GitHub Desktop.
Excessive abstraction/indirection in Ruby
# Problem set: You need to instantiate `Pages` from `Files`,
# picked up using the standard `File.read` method.
#
# For whatever reason, the `path` is the filename.
# and the `data` are the contents of the File.
# ----
# Modelling:
#
# To decouple the persistence layer and the domain layer,
# I introduce a new class `Entry` which acts as an intermediary.
# This prevents `Person` from knowing anything about `File`.
# This should allow arbitrary storage engines and facilitate
# unit-testing without crossing over to persistence details.
#
# The reader is injected via Dependency injection(DI),
# therefore the intermediary itself doesn't know much about
# the File.reader either.
#
# ---
# Result: This is too much indirection.
# The userland API looks too convoluted.
# It needs to be explained. This is mentally taxing to make sense of.
# You're doing too much.
#
# Ask yourself:
#
# - Do you *know for a fact* that `Pages` are going to have
# multiple sources in the very-near future?
# - Why did you decide to stop there? Why didn't you abstract
# it further until you can instantiate `Page` from actual atoms?
#
# If you only think you're gonna need it:
# You Aren't Going To Need it.
#
# Every decision you make is a tradeoff. In buzzword fairyland
# we'd say you're sacrificing Locality of Behavior(LOB) for
# SoC (Separation of Concerns). These 2 are beneficial concepts,
# but they are diametrically oppposed.
#
# Use the same DI concept but get rid of the intermediary.
# Use a static factory method, i.e:
# `Page.from_entry(reader)`, where `reader` is a higher-order function,
# in this case `File.read`; exactly the same concept, just getting rid
# of the extra indirection.
#
# - http://xp.c2.com/DoTheSimplestThingThatCouldPossiblyWork.html
# - https://en.wikipedia.org/wiki/You_aren%27t_gonna_need_it
# - https://en.wikipedia.org/wiki/Fundamental_theorem_of_software_engineering
# - https://en.wikipedia.org/wiki/Abstraction_(computer_science)
#
# "When you go too far up, abstraction-wise, you run out of oxygen.
# Sometimes you just don’t know when to stop, and you
# create these absurd, all-encompassing, high-level pictures of the universe
# that are all good and fine, but don’t actually mean anything at all. "
#
# From: Don't let the Architecture Astronauts Scare You
# Date: 21, 2001 by Joel Spolsky
class Page
def initialize(path, data)
@path = path
@data = data
end
def self.from_entry((path, data))
self.new(path, data)
end
end
class Entry
def self.new(path, data)
[path, data]
end
def self.from_path(reader)
->(path) { new(path, reader.call(path)) }
end
end
persons = Dir.glob('*.txt')
.map(&Entry.from_path(File.method(:read)))
.map(&Page.method(:from_entry))
# Simple, sophisticated and 100% unit-testable.
# Much better 🤌
#
# Note: Because it passes higher-order lambdas around
# it is still fairly advanced for a junior, therefore
# you couldn't delegate such a project easily.
#
# But for all intents and purposes this is more than
# Good Enough; you should stop optimizing any further;
#
# - https://deviq.com/laws/law-of-diminishing-returns
class Page
def initialize(path, data)
@path = path
@data = data
end
def self.from(reader)
->(path) { new(path, reader.call(path)) }
end
end
Dir['*.txt'].map(&Page.from(File.method(:read)))
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment