HN user

bulatb

1,103 karma

I'm easy to find. <my first name>@<my last name>.com

Posts1
Comments377
View on HN

My point to the OP is the same thing I'm saying to you. "I understand the thing and think it's fine" does not imply that nobody who understands the thing can think it isn't fine, or anyone who doesn't like the thing just doesn't understand it.

It's not some deep or interesting idea, but it needs to be repeated any time "git gud" is used as an excuse for Git's UX, which is always.

I'm sorry (like actually sorry) if I'm getting into counter-condescension here, but, like...

all you have to do to make is pleasant

This is an assumption about what I find unpleasant and why. I take it you think "reading information" and "understanding instructions" are some of those things I don't like. Your conclusion that I must not understand is based on that assumption.

The assumption is wrong.

If you can grant me that, my problem looks different. If you can't or won't, my summary was right.

Let me eat the crow of getting drawn back in and cap this conversation with a summary. I think this is important because criticizing Git's UX is always like this.

OP: No one should be worried about using Git to do a thing.

bulatb: I'm worried it will be unpleasant.

seba_dos1: Here is what will happen when you do the thing.

bulatb: I know, but doing it will be unpleasant.

seba_dos1: You must not understand what's going to happen.

bulatb: I do. The process is unpleasant.

seba_dos1: You must not understand what's going to happen.

I understand how rebase works and use it many times a day. Thanks for trying to explain but I won't respond further.

Such as moving down a fixup during interactive rebase when it's going to conflict with parents, or I've added more commits mid-rebase, or the rebase started in a tool and now I need to think about the tool's command and understand how many times the rebase point is view is backwards to interpret which is "ours" and which is "theirs" and whether it's the tool, my editor, or my own experience I should ignore because it's going to mislead me.

None of that word salad should matter, but it does. Git will ruin everything with glee and there is always an excuse for why that's fine and it's the user's fault.

I can fully understand a conflict, know it's coming, and fear it anyway because I'll have to deal with Git's behavior to fix it.

On The <dl> (2021) 2 months ago

nothing in the stack wants you to do that

Meaning treat semantic markup as a principle. Most modern tools are not that interested in 2010s ideas about separation of design and content.

On The <dl> (2021) 2 months ago

HyperText Markup Language is semantic. You're marking up a document to show what information it contains, where it contains it, and how it relates to other documents or information. Reading markup causes browsers to display things, but that's incidental.

In theory.

In practice, no one cares about semantics and the choice of tags is based on how a target set of browsers happens to display them.

Your question about who interprets markup is exactly right. In theory, you have no idea. Maybe it's a browser, maybe it's assistive software, maybe it's a browser printing, maybe it's some custom knowledge base with different views of documents for different users. In theory, you're supporting all of that by only marking up semantics and allowing the consumer to interpret them, because there isn't one right answer.

In practice, your client is Chrome.

In theory, that's irrelevant because you're using CSS to style semantic markup so it works in Chrome and still makes sense to other clients.

In practice, you're writing a web application, using a framework, and nothing in the stack wants you to do that.

On The <dl> (2021) 2 months ago

Too late to edit now, but I should mention putting titles in a list like that isn't valid. It's just to shorten the example.

On The <dl> (2021) 2 months ago

A <ul> is a list of things whose order makes no difference to its meaning. Rearranging a <ul> would change the presentation, not the information. Rearranging an <ol> would change both.

  <ul> Players
    <li> Alice
    <li> Bob
    <li> Carol
  </ul>

  <ol> Leaderboard
    <li> Bob
    <li> Alice
    <li> Carol
  </ol>

We're taking about different things. To take responsibility is volunteering to accept accountability without a fight.

In practice, almost everyone is held potentially or actually accountable for things they never had a choice in. Some are never held accountable for things they freely choose, because they have some way to dodge accountability.

The CEOs who don't accept accountability were lying when they said they were responsible.

There's nothing to prove. Responsibility means you accept the consequences for its actions, whatever they are. You own the benefit? You own the risk.

If you don't want to be responsible for what a tool that might do anything at all might do, don't use the tool.

The other option is admitting that you don't accept responsibility, not looking for a way to be "responsible" but not accountable.

You would have an API that makes the query shape, the query instance with specific values, and the execution of the query three different things. My examples here are SQLAlchemy in Python, but LINQ in C# and a bunch of others use the same idea.

The query shape would be:

  active_users = Query(User).filter(active=True)
That gives you an expression object which only encodes an intent. Then you have the option to make basic templates you can build from:
  def active_users_except(exclude):
      return active_users.filter(User.id.not_in(exclude)
...where `exclude` is any set-valued expression.

Then at execution time, the objects representing query expressions are rendered into queries and sent to the database:

  exclude_criterion = rude_users()  # A subquery expression
  polite_active_users = load_records(
      active_users_except(exclude_criterion)
  )
With SQLAlchemy, I'll usually make simple dataclasses for the query shapes because "get_something" or "select_something" names are confusing when they're not really for actions.
  @dataclass
  class ActiveUsers(QueryTemplate):
      active_if: Expression = User.active == true()

      @classmethod
      excluding(cls, bad_set):
          return cls(
              and_(
                  User.active == true(),
                  User.id.not_in(bad_set)
              )
          )

      @property
      def query(self):
          return Query(User).filter(self.active_if)

  load_records(
      ActiveUsers.excluding(select_alice | select_bob).query
  )

Prideful people who've defined themselves as being good, and arbitrarily decided being rational is good, conclude that since they're good, and rational is good, they must be rational.

At the risk of becoming that guy from the tabs vs. spaces meme: Why not both? Name folders by domain or topic and files by role, if you can.

  app/
    iam/
      orgs/
        models.xyz
      roles/
        models.xyz
Then any editor with file search can produce the type-based structure on demand.

Just a heads-up if you haven't seen it: Overriding lazy-loading options at query time can help with overfetching.

    class Author(Model):
        books = relationship(..., lazy='select')

    fetch_authors = select(Author).options(raiseload(Author.books))
Anything that gets its Authors with fetch_authors will get instances that raise instead of doing a SELECT for the books. You can throw that in a smoke test and see if there's anything sneaking a query. Or if you know you never want to lazy-load, relationship(..., lazy='raise') will stop it at the source.

https://docs.sqlalchemy.org/en/20/orm/queryguide/relationshi...

If civil engineers could launch their dams in debug mode instead of flying out and crawling through a tube, I think they would. They just can't.

Though some would still prefer the tube. That's up to them.

Service tubes and jack points and JTAG and whatever ship in physically assembled artifacts because they have to, not because they should.

It's the Thing module's responsibility to correctly set up the test doubles involved.

But why?

Maybe there's some fundamental difference of opinion here, because I'm thinking definitely not, no way, test-only code paths should never reach prod, and code that ships should never even know that testing is a thing that happens.

Honestly I'd probably resist this too. Maybe you could practice on me? Help see the benefit?

I'm seeing three main points in this advice:

1. Don't misuse mocks. (Mocks, fakes, whatever.)

2. Write testable, well-factored code with pluggable dependencies.

3. Don't use mocks at all. Use "nullables" instead.

I'm totally on board with (1) and (2) as good advice in general, no matter how you're testing. But (3) seems like an independent choice. What benefit does (3) specifically deliver that you wouldn't get with (1) and (2) and mocks instead of nullables?

I'm ready to admit I'm missing something, but I don't see it.

I understand not everyone agrees on what "repository" means. The session is a UoW (at two or three levels) and also a repository (in the sense of object-scoped persistence) and also like four other things.

I'm sort of tolerant of bits of Session leaking into things. I'd argue that its leaking pieces are the application-level things you'd implement, not versions of them from the lower layers that you need to wrap.

When users filter data and their filters go from POST submissions to some high-level Filter thing I'd pass to a repository query, what does that construct look like? Pretty much Query.filter(). When I pick how many things I want from the repository, it's Query.first() or Query.one(), or Query.filter().filter().filter().all().

Yes, it's tied to SQL, but only in a literal sense. The API would look like that no matter what, even if it wasn't. When the benefit outweighs the cost, I choose to treat it like it is the thing I should have written.

It isn't ideal or ideally correct, but it's fine, and it's simple.

I mean that from the point of view of YAGNI for a small app. For a big one, absolutely, you will find the places where the theoretical distinctions suddenly turn real. Decoupling your data model from your storage is a real concern and Session on its own won't give you that advantage of a real repository layer.

SQLAlchemy is flexible, though. You can map a Person from three tables if you need to. It's a data mapper, then a separate query builder on top, then a separate ORM on top of that, and then Declarative which ties them all together with an ActiveRecord-ish approach.

I prefer not to use ORMs but just write queries and map the results into value objects. That work I would put into a Repository.

Yep, I hear ya. Maybe if they'd built on top of something lower-level like stdlib sqlite3, it wouldn't be so tempting to dismiss as YAGNI. I think my comment sounded more dismissive than I really meant.

In theory it's a nice abstraction, and the benefit is clear. In practice, your repository likely ends up forwarding its arguments one-for-one to SQLAlchemy's select() or session.query().

That's aside from their particular example of SQLAlchemy sessions, which is extra weird because a Session is already a repository, more or less.

I mean, sure, there's a difference between your repository for your things and types you might consider foreign, in theory, but how theoretical are we going to get? For what actual gain? How big of an app are we talking?

You could alias Repository = Session, or define a simple protocol with stubs for some of Session's methods, just for typing, and you'd get the same amount of theoretical decoupling with no extra layer. If you want to test without a database, don't bind your models to a session. If you want to use a session anyway but still not touch the database, replace your Session's scopefunc and your tested code will never know the difference.

It's not a convincing example.

Building your repository layer over theirs, admittedly you stop the Query type from leaking out. But then you implement essentially the Query interface in little bits for use in different layers, just probably worse, and lacking twenty years of testing.