News for hackers. Stuff that matters.
Re:Yagni (2015) by camgunz on Sunday August 12, 2018 @02:46PM (#17745782)
I think coherency of implementation is a lot more important than YAGNI. For example:

  class Car:

      def honk(self):
          print('Honk!')
Cool, now we have a car, but it only honks. But we only needed it to honk, so that's fine.

  class RoadTrip:

      def __init__(self, car, destination):
          self.car = car

      def go(self):
          self.car.start()
          self.car.drive_to(destination)
Oh no, we built a car but it does nothing that a car does. Let's add this functionality.

  class Car:

      def __init__(self):
          self.started = False
          self.gps = []
          self.location = cool_app.get_current_location()

      def honk(self):
          print('Honk!')

      def start(self):
          self.started = True

      @property
      def location(self):
          return self.gps[-1]

      @location.setter
      def location(self, new_location):
          self.gps.append(location)

      def drive_to(self, location):
          self.location = location
Super easy. Except a different user of our library implemented this functionality already for a different reason, but in a different way.

  class CarEngine:

      def __init__(self, car):
          self.car = car
          self.started = False

      def start(self):
          if self.started:
              raise Exception('Already started!')
          self.started = True

      def turn_off(self):
          if not self.started:
              raise Exception('Not started!')
          self.started = False

  class CarGasTank(self, car):

      def __init__(self, gallons):
          self.car = car
          self.capacity = gallons
          self.level = gallons

      @property
      def empty(self):
          return self.level == 0

  class MotorCar:

      def __init__(self, engine, gas_tank, mpg, location):
          self.engine = engine
          self.gas_tank = gas_tank
          self.location = location
          self.mpg = mpg

      @property
      def started(self):
          return self.engine.started

      def start(self):
          return self.engine.start()

      def drive_to(self, location):
          distance = location - self.location
          fuel_required = distance / self.mpg
          if fuel_required > self.gas_tank.gallons:
              raise Exception('Not enough gas!')
          self.gas_tank.gallons -= fuel_required
          self.location = location
I've lost count of the number of times I've seen stuff like this. Sometimes the original implementation is in a different library that's hard to change. Sometimes other code rely on specific details of the original implementation so changing it requires changing that code too. Sometimes additions seem "out of scope" so they are actively pushed to dependencies.

It might feel like this is a process problem -- like there should have been better upfront design or communication -- but these "failure cases" are actually the success cases for code. You want code to rely on your libraries. You want to consider all users of your libraries when making changes.

The problem is actually YAGNI, because that mentality encourages us to churn out big, incoherent bags of functions when we should be thinking about code responsibilities and designing whole systems.

You can see this in action with JavaScript most infamously. Its standard library is extremely YAGNI, and it led to whole new programming languages being built on top of it because it was so anemic. The complexity has to live somewhere, and if you don't deal with it in a coherent and orthogonal way, someone else will have to deal with it and their options will be a lot more limited than yours.

Following simple mantras like DRY, YAGNI, and whatever else fits on a poster is the surest way to mess up your design. It requires thoughtfulness and experience, and there are no shortcuts.

There are no comments on this comment yet.