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

Yeah, nobody's going to use those logging functions. They're too cumbersome and take too much effort. And they're completely unsuitable for library code, because the logging target (stdout/stderr/file/socket) and formatting are fixed and not flexible.


So you'd rather use the example in the article that's 5x longer? Makes no sense.


Imagine 1000 call sites use 1 function. What would you choose: 1 slightly more complex function which is easy to use by 1000 callers (because the function signature is clean and the dependencies are already injected somewhere in the constructor so they're hidden from the callers) or 1 simple function which is hard to use by 1000 callers (because they need to pass implementation details around)? A function is defined once but used N times, so it makes sense to optimize for the more common case (usage).


I addressed most of this in our thread. You might as well go back to that. Let me converse with this guy on his own terms.

Anyway in response to your reply. It's the same thing. Maybe another renamed example will help you understand. A LoggerSocket class is identical to a WrappedSocket class. It's a wrapper around a socket which is basically similar to a socket itself, so let me call it such.

In your method it's:

     wrappedSocket1 = LoggerSocket()
     wrappedSocket2 = LoggerSocket()

     doStuff(wrappedSocket: LoggerSocket){
         wrappedSocket.log("blah")
     }

     doStuff(wrappedSocket1)
     doStuff(wrappedSocket2)
in my example it's:

    wrappedsocket1 = Wrapper(Socket())
    wrappedsocket2 = Wrapper(Socket())

    doStuff(wrappedsocket: Socket){
        log(wrappedsocket, "blah")
    }

    doStuff(wrappedsocket1)
    doStuff(wrappedsocket2)
You see the equivalence? The amount of code is the same. The reuseability of the code is not the same as WrappedSocket is more general then LoggerSocket, but BOTH need to be passed everywhere, and editing the core methods/definitions of log() or LoggerSocket has basically the same effect


You're doing it wrong: you pass LoggerSocket as a function argument. You're trying to use it in a very procedural way: you create a logger of a concrete class, you pass it to a function which expects a concrete class, you call a function on it. Of course it will be the same, and we have none of the benefits of OOP in this example. In a real project, the logger will already be preconstructed somewhere else (DI framework, for example, but manual initialization somewhere at startup works as well) and stored as an object field of the current class (in a functional language that would be a captured value). So at most of the call sites it's going to be just this.logger.log("some error happened") without any mention of sockets or any other kind of boilerplate/implementation details. Our client class has a single responsibility by delegating additional work to a dependency defined as a simple protocol ("interface") in its constructor, which is useful for readability and modularity (you can inject anything you want, provided it implements the protocol), and in case implementation details of the dependency change, we don't have to modify every call site. Another thing is that your code is not testable because you can't swap the socket with a mock (at least, in languages with strong/static typing). You can implement all this with structs and function pointers in something like C, the difference here is that object-oriented languages have nice syntax sugar for it.


>You're doing it wrong: you pass LoggerSocket as a function argument.

This is because I'm too lazy to write out a full class definition for DI. The intent of my example still stands. I'm writing a lot, and a lot of examples too, so forgive me if I don't write out the entire DI pattern of passing ownership of the Dependency to a class.

>In a real project, the logger will already be preconstructed somewhere else (DI framework, for example, but manual initialization somewhere at startup works as well) and stored as an object field of the current class (in a functional language that would be a captured value).

Bro, just pretend that the doStuff function is a constructor and that it also happens to do more stuff then I wrote, like maybe assign it to a member variable if that helps you understand better.

>So at most of the call sites it's going to be just this.logger.log("some error happened") without any mention of sockets or any other kind of boilerplate/implementation details.

I would say the logger instance is a boiler plate detail. It's a wrapper around all the dependencies. WrapperSocket/LoggerSocket etc. Ideally what we want is just log(string). Too bad.

>Our client class has a single responsibility by delegating additional work to a dependency defined as a simple protocol ("interface") in its constructor, which is useful for readability and modularity (you can inject anything you want, provided it implements the protocol),

The client class has more then one responsibility. It has to log something AND manage dependencies. That's two responsibilities. You can argue for the interpretation of responsbilities, but think about it like this. Compared with a stateless function that doesn't MANAGE ANY STATE, a stateless function is even more "single" in terms of responsibility.

I'm going to stop responding to this thread here. Most of the points are in the other thread, pointless to have two. Just go back to the other thread, state your points, and I will respond.




Consider applying for YC's Winter 2026 batch! Applications are open till Nov 10

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

Search: