Hacker Newsnew | past | comments | ask | show | jobs | submitlogin

Your thermos does not have a switch which you must set to "hot" or "cold" before inserting a liquid, handles solids as well as liquids, and doesn't require you to even think where the theshold might lie between the "hot" and "cold" settings. Instead it just does its best to prevent an energy exchange in either direction without having to even know the variables involved. And the "same subroutine" is basically used for different sized thermoses.

In code I see people not understand this all the time. Here's an example: let's say you present the user with their previous orders, and give them the option to filter those orders by some criterion.

The shitty way to do this is to have two variables:

    Filter* orderFilter = NULL;
    bool filterSpecified = false;

    void setOrderFilter (filter* newFilter) {
      orderFilter = newFilter;
      filterSpecified = true;
    }
So instead of just checking if a filter has been assigned, you have a separate boolean. What happens if the boolean is true but the filter is null? What would the vice versa case even mean?

The global codebase is riddled with dumb errors like this.



Even better, let's just have the default orderFilter be the equivalent of '*'.

If there's always a filter, then there's no longer a branch at the point it's used -- that needs to be tested and maintained.

I used to work with a programmer who I found difficult because their code was an ever-increasing number of "if" statements as each new case came along. Conversely he would say coding is relatively easy and that I was overcomplicating a problem by thinking about it; all I needed to do is "add an if statement here".


Yep! On of the things I point out most frequently in code reviews are things like this. However, null is an imperfect system for capturing such state. It's not self-documenting, and it breaks down when you have more than 2 states to represent.

Languages with Sum Types represent this much more elegantly with arbitrary numbers of variants and force you to check which one you have before accessing the more specific data (e.g. the filter) inside.


I deeply disagree!

Our code is riddled with heavily overloaded meanings in a single value/domain, and if you have a variety of filters, you end up with this atrocity:

  Foo *f1; // hey, just test for NULL
  double f2; // hey, just use isnan()

  int f3; // aaaaah, crap
  bool f3_specified; // god why

  std::string f4;
  // requires heavy drugs to solve existential catastrophes
Compare that to

  template <typename T>
  struct Filter {
    bool is_specified;
    T value;
  };

  Filter<Foo *> f1;
  Filter<double> f2;
  Filter<int> f3;
  Filter<std::string> f4;
And the variety of code that has to work with either of these examples.

What happens if the boolean is true but the filter is null?

An assertion fails miserably.

What would the vice versa case even mean?

That an assertion failed miserably.

What happens if the filter is one? Minus one? Equals to PC(IP), BP? If NULL filter has to search for NULL values in a dataset? If we are looking for NAN values in a corrupted one (this one is even more tricky)?

Deeply!


I'm not sure they were suggesting using type-specific sentinel values (like null and nan), but to always use null or point to a value.

I like wrapping the information in a data structure exactly as you suggested, and if you never mutate the dereferenced pointer, it's equivalent to what they proposed. Big if, though.

The best is to do this generically with https://en.m.wikipedia.org/wiki/Option_type , since this pattern comes up regularly, not just with this one domain concept of "Filter"


Another way of expressing this concept is to "make invalid states unrepresentable"


Dijkstra’s EWDs are full of examples of thinking about problems mathematically instead of operationally and then deriving elegant procedures.


Do you have any examples to point to?



100% agree - it's a quite common trap for inexperienced developers to write down every cases and conditions separately, when they shouldn't. As a similar example, instead of writing "req.source = host + port", people would write:

    if (host == "load-balancer-1.internal.dns") {
        req.source = HOSTS::LOAD_BALANCER_1;
        req.source += PORT_MAPPING[HOSTS::LOAD_BALANCER_1];
    }
    else if (host == "load-balancer-2.internal.dns") {
        // repeat
    }
After a few quarters of services, layers, and people being added and removed, it becomes a 500-line monstrosity and sits in every critical path. And because now it's a 500-line class (half of which is defunct, but good luck figuring out which half), nobody has time to read through it and figure out that it should have been a single assignment statement.


I think this is one of the areas that functional programming naturally steers the programmer in the right direction.


Having the two separated gives the ability to disable/re-enable the filter without losing it. That's often very useful. In other cases you're very right.


Then you'd go with `filter_enabled` or similar, which is an entirely different, separate flag. It is not coupled to what the filter itself looks like.


this is the essence of Linus Torvald’s doctrine of good taste




Consider applying for YC's Fall 2025 batch! Applications are open till Aug 4

Guidelines | FAQ | Lists | API | Security | Legal | Apply to YC | Contact

Search: