HNHacker News
TopNewBestAskShowJobs

bulatb

1,130 karma · joined May 28, 2012

I'm easy to find. <my first name>@<my last name>.com
submissionscomments
bulatb··on Settling the File Structure Debate
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.
bulatb··on DigitalOcean blocks SMTP ports 465 and 587 since last month
It looks like this is only for accounts created after June 22, 2022. Mine are older and their emails have been going out before and since.

I never even heard about this.

https://www.digitalocean.com/blog/smtp-restricted-by-default

bulatb··on Architecture Patterns with Python
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...

bulatb··on Testing Without Mocks: A Pattern Language (2023)
The answer to your needlessly aggressive questions is, no, of course not. None of that is anything I'm saying.

I'm genuinely sorry if it's frustrating, hopefully not, but I'm exiting this conversation now.

bulatb··on Testing Without Mocks: A Pattern Language (2023)
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.

bulatb··on Testing Without Mocks: A Pattern Language (2023)
Thanks, this helped me understand some things. Unfortunately I don't have time to write a real reply right now, but I think I'm more convinced than before. Thanks for taking the time.
bulatb··on Testing Without Mocks: A Pattern Language (2023)
It's just a feeling, not a dogma. I'll happily throw it away. But why?
bulatb··on Testing Without Mocks: A Pattern Language (2023)
> 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.

bulatb··on Testing Without Mocks: A Pattern Language (2023)
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.

bulatb··on Architecture Patterns with Python
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.

bulatb··on Architecture Patterns with Python
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.

bulatb··on Architecture Patterns with Python
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.

bulatb··on Better Shell History Search
One could say the same thing about empathy, another "basic" skill.

Imagine how you'd rush to justify your lack of it here.

Everything you'd say in your defense would equally explain why someone wouldn't know the meaning of a certain unmarked number in the output of a random shell command.

bulatb··on ChatGPT can't kill anything worth preserving
Nobody is thinking about qualia. That's only how I framed it. They just know the machine will never replace them. They're a person, doing person things, and it's a machine.

So every time it proves it can, they move the goalposts and invent a truer Scotsman who can never be replaced by a machine. Because they know it can't do person things. It's a machine.

bulatb··on ChatGPT can't kill anything worth preserving
Qualia might still be magic. Maybe we have souls or something, I don't know.

I'm comfortable with saying that this way of answering the question doesn't work, because the argument is simply, "How can it be false if I believe it's true?"

bulatb··on ChatGPT can't kill anything worth preserving
You can't interrogate your qualia. A lot of people think (or really, feel) that makes them magic.

It doesn't.

But if you feel they're magic, you'll never believe that a "random" mechanical process, which you think you can interrogate, could ever really have that spark.

bulatb··on Show HN: We made an MCP server so Cursor can debug Node.js on its own
> Cursor often gets into "dead loops" trying to fix code [1][2]. But, Cursor also seems to get out of dead loops when it adds `console.log` statements everywhere.

Hah. That sounds like just... debugging.

bulatb··on Trapping misbehaving bots in an AI Labyrinth
The comment I responded to. I take it to be musing from the bailey.

Maybe I'm wrong, I don't know.

I'm just a person who keeps hearing, from every direction, "Won't someone please think of the assholes?"

---

Edit: My comment above used to say what was quoted. I changed it to be more precise about my issue with the comment I replied to.

bulatb··on Trapping misbehaving bots in an AI Labyrinth
We've got to stop with this.

Bad actors acting in bad faith, causing damage? Well... you know, it's just how they are... They have a right... to... Who's to say they're really bad? You know? I mean just look at that guy over there. What about him?

Good actors, fed up, responding in a way that doesn't cut the willful hostiles every bit of slack you can imagine, which potentially could maybe cause a little bit of damage, which would stop as soon the attack was over? Punish them, they'll ruin everything.

bulatb··on Use Long Options in Scripts
Yeah, "safe_exec" is a useless name without context. But the context was you need to call a program from another program. Many people would call system() or whatever because usually it's obvious and easy, and the pitfalls are less so.

Shelling out is not the only option. People are just saying not to use that option. Better ones won't save you if you purposely do something stupid. They will save you if the user wants to trick you into doing something else.

bulatb··on Use Long Options in Scripts
Any time you send commands and data down a single channel, user input that's intended to be data can be misinterpreted as a command. For example, if your program wants to:

    run("program --option '{user_input}' > file")
to save some input to a file, and the user's input is:

    '; bad_command #
then when run() sends that string to the shell, the shell will run:

    program --option '';
    bad_command #' > file
Most languages have something like a safe_exec() that separates the shape of the command from the values of the options, executing "program" with the options and the user_input in the arguments array as data. Skipping the shell step, which would just be building an exec call anyway, removes the opportunity for users to confuse it into doing something else.

The list-based API alternative they recommend might look like this:

    safe_exec(["program", "--option", user_input], stdout="file")
and it would always exec "program" with argv[1] == "--option" && argv[2] == user_input. If the user_input happens to be:

    '; bad_command #
...well, then, the user can enjoy the contents of their file.
bulatb··on The Future Is Niri
> That is almost certainly user error on my part

If an interested and reasonably savvy person can't get a program to work as it claims, the problem is the program, not the user.

bulatb··on JJ Cheat Sheet
Great! Happy to help.

Git is like that Brooks quote became software. "Show me Git commands and I'll continue to be mystified; show me history and I won't need your commands."

The official book on their website goes through most of Git like this, if you want to know more. It's really night and day once you know what it's actually doing.

bulatb··on JJ Cheat Sheet
Rewriting a commit in Git (with rebase, --amend, --squash, whatever) creates a new commit with your changes, keeping the original around but detached from the branch. For example, amending the tip of a feature branch (git checkout feature; git commit --amend) turns this:

  A --- B --- C <- [main]
         \
          X --- Y --- Z <- [feature]
into this:

  A --- B --- C <- [main]
         \
          X --- Y --- Z' <- [feature]
                 \
                  Z

The old commit is not destroyed, just taken off the path you walk from "feature" back in time. Even if you `rebase -i main` on "feature" and drop Y and Z, they'll still be around, just not in your branch:

  A --- B --- C <- [main]
         \     \
          \     X' <- [feature]
           \
            X --- Y --- Z
If you're worried about rebase going bad, before you start, create a temporary branch (git checkout -b before-risky-rebase; git checkout -) to mark that line of history where "feature" points at the good state.

  A --- B --- C <- [main]
         \
          X --- Y --- Z <- [feature,before]
If anything goes wrong that `rebase --abort` doesn't fix, get out of there somehow and `git checkout before-risky-rebase`, or, on "feature," `git reset --hard before-risky-rebase`. Here, the backup branch "before" is still what "feature" was before the rebase:

  A --- B --- C <- [main]
         \     \
          \     X' --- (bad) --- (oops) <- [feature]
           \
            X --- Y --- Z <- [before]
As long as you don't force-push anything, it doesn't really matter if you damage the now-broken branch even more while getting out. Reset "feature" to your backup and it never happened. You can even damage "main" and still `reset --hard` to origin/main (if you have one) or the tip "main" had before you broke it.

Even if you don't remember to create the backup branch, the hashes of your old commits now bypassed are still in the reflog. You can always find the hash where "feature" used to point and manually move the pointer back:

  git checkout feature
  git reflog
  (find the hash)
  git reset <hash> # --hard or --mixed if needed
Not that this is obvious or trivial or anything. It shouldn't be this hard. But your commits are safe from almost any way you might destroy them once they're somewhere in your history, at least until unreachable commits are eventually garbage-collected.
bulatb··on The Alpha Myth: How captive wolves led us astray
People didn't get their worldview from the myth. They like the myth because it fits their worldview.
bulatb··on It's time to make computing personal again
When someone can't accept that other people value different things than they do, and that person also has uncommon values, they will spend a lot of time upset that everyone is wrong. They'll look for something that explains why everyone is choosing to be stupid and evil, and they'll try to find that comfort in the worldview that made them upset in the first place, retreating ever further from the chance to get real answers.

When people with that kind of worldview roll their eyes at empathy, or scoff at any need to see beyond their own opinions[0], they are all but guaranteed to seal themselves inside.

FOSS, decentralized, et al. attract a lot of people with those worldviews, and that story is the story of them failing with consumers over and over and over, doubling down on what failed every time.

[0] https://news.ycombinator.com/item?id=29114882

bulatb··on VS Code Pets
That unholy petvar symbiosis owns the codebase like a cat owns your house. The program and the company are now in service of minTaxRateOffsetTemp.
bulatb··on Formal Methods: Just Good Engineering Practice? (2024)
I was struggling to find the word, but I think I do mean "declarative." It's also deductive, but I'm looking at it from the point of view of what I'd have to do if I were proving something. I'm on board with testing being formal as in algorithmic.

Seems like there's domain-specific terminology (almost wrote "language"!) I'm using accidentally or incorrectly and making a mess. I might have stepped in it with "model." Mainly I just think I see a difference between ways of verifying where the work is doing and the work is being. But it sounds like I don't know enough to have this conversation.

Thanks for humoring me, though.

bulatb··on Formal Methods: Just Good Engineering Practice? (2024)
I'd argue there's a qualitative difference in approach between testing, a procedural approximation to a proof of absence of bugs by exhaustion, and the more declarative (more "formal") method of directly proving things about a model. Might be splitting hairs, though.

It reminds me of the question about whether programming is math. Depends on what you think you're doing. Are you doing things with state to get results, or building a system of facts that results in the answer?

bulatb··on When will we fix the tools that run the world?
Yes! This, this, this. Moralizing is the opposite of problem-solving. Assuming is the opposite of understanding.

When combined you get a fundamental metaproblem that:

1. You can't solve a problem you don't understand.

2. Moralizing is more satisfying than understanding.

3. This is a problem, which can't be solved if people choose to moralize instead of understanding.

← PreviousPage 2 of 13Next →