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

I work in security, and get to interact with a lot of area tech lead and architect types (L7-L9 in FAANG terms). I have found that the people who most concern themselves with “proper design” are generally about 5-10 years into their career and trying to prevent juniors from shooting themselves in the foot, but don’t yet have a proper appreciation for the cost of complexity and how code changes over the long run. Conversely, after you’ve been doing this for 20+ years you learn to value simplicity over most other technical factors.

Questions like “how early do we branch” and “what is the actual job this has to do” end up being the ones whose answers tend to have the most long-term value. Questions about abstractions and encapsulation tend to lead to the type of argument you describe. And the “the big picture” people are the ones with the most security issues in their code, because they don’t really understand what the properly architected codebase is really doing.



This resonates with my experience. Apparently now the "inversion of control", "dependency injection" patterns are in vogue. So my Sr. Developers are building new code with all that crap. So much that when you want to make a simple change, you have to hunt down 20 files of `xxxCreator`s, `xxxHandler`s and `xxxProvider`s that are glorified functions invoked by `.execute` or `.call`.

And this is in TypeScript ... we were laughing about that kind of complexity in Java 20 years ago... in Slashdot.


I always deplore this shallow influencing of design patterns without proper examination of whether said design pattern fits or is even correctly described by the popularizer. People fail to realize that inversion of control is necessary ONLY if some controls need to be inverted.

To be fair, "inversion of control" is a great concept, but unfortunately told in a very specific way across time so the meaning now has changed into an inferior form of the original.


I do personally think it's preferable for components to have their dependencies injected in some way, and that it's better to have initialisation logic (especially related to heavy weight components) in one place.

That doesn't mean that you need some huge ass meta-framework for it though. Just have your main function assemble the components together, done.


Even this is an over simplification of inversion of control.

Dependency injection is one form, but even a library that exposes parameters is also a form of inversion of control.

The degree of control is what varies, whether it is a mere flag (boolean), a variant directive (complex struct), a turing complete construct (pointer of instantiated function) or anything in between.

What I am saying is that this concept has been reduced as it is popularized and it isn't doing justice to the original powerful concept.


> Questions like [...] “what is the actual job this has to do” end up being the ones whose answers tend to have the most long-term value.

How is that not "big picture", proper design and abstraction/encapsulation?

You seem to be arguing against a strawman. I didn't write "complexity". But if you want to fix security issues in your database access patterns, you better not have SQL distributed across your whole codebase.


I don't think I am. You seem to disagree that things like where to branch are worth talking about, but you haven't given examples of what you mean by the big picture that is worth talking about. In this thread, you've told a few people they don't understand what you're saying - so what are you saying?

Re: SQL. Maybe? Maybe not? It's a lot easier to do bad things to a database if you let an ORM generate the query. Writing SQL directly is not an anti-pattern, as long as you parametrize the query. But then again, maybe an ORM is not what you're suggesting - once again, you're saying what you don't like, and others are left to draw conclusions about what you think is good style.


1. Yes, I happen to think that the difference between this:

  fn f() {
    if foo && bar {
      if foo {

      } else {

      }
    }
  }
and this:

  fn g() {
    if foo && bar {
      h()
    }
  }

  fn h() {
    if foo {

    } else {

    }
  }
is not worth talking about.

2. If we're talking about things like validate user input at the boundaries (and if you're a library developer, "user input" might refer to what the library users will pass in to your code), then I think that is a valid and useful guideline. If we talk about "don't spend time executing a bunch of code when you can exit early", then I'll also agree, although in most contexts, this doesn't really matter at the granularity of functions - but it does matter that you e.g. reject invalid input early on in your request handling (assuming a web app here) and not start processing it before deciding that you don't need to (with the notable exception of password validation, which should always be constant time).

3. What I mean by "big picture" is things like clearly delineating which parts of the codebase are responsible for what - for example, which parts of the code contain logic related to the database, which parts are related to business logic, which parts to interfacing with external APIs, which parts about handling responses. It's things such as "functional core, imperative shell" (Gary Bernhard) or other means to try to manage side effects. It's deciding whether to write blocking or non-blocking code, and in the latter case, how exactly to go about it (instead of making a total mess by mixing both styles with a lot of back and forth). It's about deciding on how you want to structure your test suite so it gives you the maximum amount of confidence.

4. I think ORMs provide mostly false confidence and outside of prototype scenarios, I wouldn't personally use them anymore - especially not ones like Hibernate and ActiveRecord - but, alas, it's not always my decision. Their biggest flaw is IMHO that they pollute your domain model (and sometimes even your presentation logic - looking at you, ActiveRecord) with database logic, just for the convenience of being able to call "save()" directly on an object. I don't terribly mind writing raw SQL (as long as you properly parameterize inputs), although I think that generally, type-safe query builders with a 1:1 relationship to SQL (such as jOOQ) are preferable. I do think that all of your SQL-related logic in your application shouldn't be scattered randomly across 10,000 files - it should be in a dedicated place (a number of files grouped in a common folder/package/module, maybe) so that if you need to review certain aspects of it (related to security or performance, e.g. avoiding N+1 queries), you don't have to hunt that logic down throughout your whole application.




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

Search: