Should I prefer consistent design or good design?
-
I know this is probably going to be opinion based, so let's hope this is still the right category. ;)
We have... let's call it Framework A, that most of our products are built on.
I'm working on an internal tool, also built on Framework A. Right now I'm basically the only dev, but I'm trying to think ahead about bus factor, etc.
As you might expect from a 15+-year-old codebase (at least, that's the oldest date I could find in the comments), there's some cases of what would now be considered a bad design decisions. My main concern is that it obviously wasn't designed around dependency injection. There's a number of static utility classes that are doing service location or accessing HttpContext.Current, rather than demanding their needs in the constructor.
If I'm writing a new app with the framework, what's my best approach?
- Use the existing design patterns for new classes, making it easier for other devs familiar with the other framework products to adopt.
- Stick to "modern" design and make all my new classes declare their dependencies, but feel free to use existing Framework A classes as appropriate
- Inject All The Things. Don't use any sketchy framework code - either wrap it in something injected (so it can be replaced later in one swell foop) or rewrite it.
My inclination is #2, with #1 as my second choice, but I'm interested to hear others' thoughts as to the One True Way @unperverted_vixen Should Code.
-
@unperverted-vixen said in Should I prefer consistent design or good design?:
I know this is probably going to be opinion based, so let's hope this is still the right category. ;)
We have... let's call it Framework A, that most of our products are built on.
I'm working on an internal tool, also built on Framework A. Right now I'm basically the only dev, but I'm trying to think ahead about bus factor, etc.
As you might expect from a 15+-year-old codebase (at least, that's the oldest date I could find in the comments), there's some cases of what would now be considered a bad design decisions. My main concern is that it obviously wasn't designed around dependency injection. There's a number of static utility classes that are doing service location or accessing HttpContext.Current, rather than demanding their needs in the constructor.
If I'm writing a new app with the framework, what's my best approach?
- Use the existing design patterns for new classes, making it easier for other devs familiar with the other framework products to adopt.
- Stick to "modern" design and make all my new classes declare their dependencies, but feel free to use existing Framework A classes as appropriate
- Inject All The Things. Don't use any sketchy framework code - either wrap it in something injected (so it can be replaced later in one swell foop) or rewrite it.
My inclination is #2, with #1 as my second choice, but I'm interested to hear others' thoughts as to the One True Way @unperverted_vixen Should Code.
This is a good question, and I'm not sure there are easy answers. I've done a lot of maintenance programming on legacy projects, and it is indeed a problem where every six months there's a new programmer who does things differently. Especially when they use the hot new framework of the moment, which doesn't endure the test of time, so you're stuck learning yet another framework which no one uses any more.
On the other hand, 15 years is a long time, so I think it's perfectly reasonable to write a new app the way you think it should be done. This is the route I would follow.
-
@Unperverted-Vixen Whatever is most clear and readable to you and any other programmer who may work on the code. The purpose of design patterns is (or ought to be!) clarity, so if the existing system is easily understood, then I would recommend consistency. If the existing code is confusing and you have the time, then it would be better to rewrite the code to make it clear.
Related to that, if you need to have something convoluted for performance reasons, then add comments to explain what is going on in the code and why it is needed.
Don't worry too much about strictly following design patterns. They're really more what you might call guidelines than actual rules.
-
@unperverted-vixen Dependency Injection is no hill to die on. Does it offer benefits? Sure. Is it a silver bullet? God no. (Especially if it's done with some horrible black-box reflection-based library that'll malfunction and have you scratching your head for days sooner or later. Don't do that. Write the actual code that actually runs in the actual file. I hate black boxes.)
The question here is:
- What are the directives from above? (For example, have you been told it has to be 100% unit-testable?)
- What is your ability to modify Framework A? (For example, if you decide to add more testable classes, are you also able to modify Framework A classes to be more testable?)
- What do your co-workers think? They are the ones who might have to take this over, not internet randos.
-
@unperverted-vixen said in Should I prefer consistent design or good design?:
I'm trying to think ahead about bus factor
Try to not think too far ahead or you'll easily spend lots of effort on stuff that turns out to never matter. That said, keeping things consistent is a pretty good longer-term plan, as it reduces the amount that people have to learn before they can do things the right way.
-
@blakeyrat said in Should I prefer consistent design or good design?:
The question here is:
- What are the directives from above? (For example, have you been told it has to be 100% unit-testable?)
No commands to make it testable. I consider it a "nice to have" - I'd like to have unit tests where I can do so reasonably, but I don't think 100% test coverage is a hill worth dying on.
(While newer framework components do have unit tests, I don't think any other implementing projects besides mine do.)
- What is your ability to modify Framework A? (For example, if you decide to add more testable classes, are you also able to modify Framework A classes to be more testable?)
I have zero ability to modify the framework. (Oh, how I wish.)
- What do your co-workers think? They are the ones who might have to take this over, not internet randos.
At the moment I'm the only developer working on this project. But that's a good point, nothing stopping me from asking the devs on other projects what they'd like to see when taking stuff over.
-
@dkf said in Should I prefer consistent design or good design?:
Try to not think too far ahead or you'll easily spend lots of effort on stuff that turns out to never matter.
But what else am I supposed to do during pointless meetings?
-
@unperverted-vixen Discreetly watch videos of people doing the disappearing towel trick for their pets?
-
I'd say design what you can in the most normal possible way. where dependencies are easy to provide and there's no question what things need. Don't go down the insane route that involves huge blobs of dependencies that are made of other blobs of dependencies injected into everything, because that leads you to the place you're in now. Inject, but sensibly.
-
How much code are you adding? If it's just a bit, I'd go with #1.
If it's a moderate amount or a lot that you're doing, I assume you have some code that touches the older stuff a lot and some code that's fairly independent. I'd strongly consider #1 for the former and #2 for the latter.
-
@blakeyrat said in Should I prefer consistent design or good design?:
@unperverted-vixen Dependency Injection is no hill to die on. Does it offer benefits? Sure. Is it a silver bullet? God no. (Especially if it's done with some horrible black-box reflection-based library that'll malfunction and have you scratching your head for days sooner or later. Don't do that. Write the actual code that actually runs in the actual file. I hate black boxes.)
This. DI is a bunch of spooky-action-at-a-distance that throws a whole bunch of the benefits of static typing out the window. It may make some things easier to code, but at the price of making it significantly harder to debug. And since software spends far more time in maintenance than in initial development, this is a net loss, not an improvement.
-
@masonwheeler there's a lot to be said for passing in dependencies explicitly, but that doesn't require a whole framework and the action at a distance part. Just creating them where it makes sense and passing them along when it doesn't.
-
@benjamin-hall said in Should I prefer consistent design or good design?:
there's a lot to be said for passing in dependencies explicitly
Sometimes, when it's appropriate. A lot of times, we see people taking it way overboard and "DEPENDENCY INJECT EVERYTHING!!!" and violating encapsulation. Remember CS 101, where they taught you that a well-designed class should hide its internal implementation details rather than exposing them to the world? Many of the ways DI gets used these days fly directly in the face of this principle.
-
@masonwheeler said in Should I prefer consistent design or good design?:
@blakeyrat said in Should I prefer consistent design or good design?:
@unperverted-vixen Dependency Injection is no hill to die on. Does it offer benefits? Sure. Is it a silver bullet? God no. (Especially if it's done with some horrible black-box reflection-based library that'll malfunction and have you scratching your head for days sooner or later. Don't do that. Write the actual code that actually runs in the actual file. I hate black boxes.)
This. DI is a bunch of spooky-action-at-a-distance that throws a whole bunch of the benefits of static typing out the window. It may make some things easier to code, but at the price of making it significantly harder to debug. And since software spends far more time in maintenance than in initial development, this is a net loss, not an improvement.
How does it break static typing? I've never really had it make debugging harder, on either .NET or the JVM.
-
@masonwheeler said in Should I prefer consistent design or good design?:
DI is a bunch of spooky-action-at-a-distance that throws a whole bunch of the benefits of static typing out the window.
... only if it's implemented by some horrible reflection-based black-box library. There's nothing wrong with DI if you do it manually. Don't conflate the two.
-
@blakeyrat said in Should I prefer consistent design or good design?:
@masonwheeler said in Should I prefer consistent design or good design?:
DI is a bunch of spooky-action-at-a-distance that throws a whole bunch of the benefits of static typing out the window.
... only if it's implemented by some horrible reflection-based black-box library. There's nothing wrong with DI if you do it manually. Don't conflate the two.
Is that how you describe all the DI frameworks or are there particular ones you have in mind when you say that? At work we use Castle Windsor and it works pretty well for us. Some our own support code around it has turned into a bit of a mess but basic Castle usage is pretty nice.
-
@mikehurley said in Should I prefer consistent design or good design?:
How does it break static typing?
What's the type of the thing that comes out of the DI container? It's
object
, which may be hidden by a generic method that casts it to whatever you're using and make it look like everything is statically typed, but nowhere is there any guarantee that the thing you're requesting to be injected actually exists inside the DI container.If you weren't using DI, you could do the same thing any number of ways, from local instantiation all the way to having a global resource, (not saying that that would be good, only that it would be possible,) and if you tried to refer to something that didn't actually exist, the compiler would catch it. DI takes this protection away.
-
@unperverted-vixen said in Should I prefer consistent design or good design?:
But what else am I supposed to do during pointless meetings?
I glued thumb tacks to the bottom of the table top in front of my seat. When I started to nod off, I could press my fingers on the points.
-
@masonwheeler Please please please please please when talking about Dependency Injection as a concept, do not conflate it with "a horrible bloated reflection-based scary-action-at-a-distance" library that bad programmers use to implement Dependency Injection.
Mason is obviously talking about the latter.
DI itself doesn't require anything other than your fingers on your keyboard wring plain-to-see lines of code that the debugger will trace over with no problems at all.
That said:
@masonwheeler said in Should I prefer consistent design or good design?:
What's the type of the thing that comes out of the DI container? It's object,
Even the crappy, crappy DI library I was forced to use at a previous job wasn't that crappy. It'd only do its "magic" with interfaces, and kept a list of the "default" implementation of the interface. So you never got
object
.
-
@masonwheeler said in Should I prefer consistent design or good design?:
@mikehurley said in Should I prefer consistent design or good design?:
How does it break static typing?
What's the type of the thing that comes out of the DI container? It's
object
, which may be hidden by a generic method that casts it to whatever you're using and make it look like everything is statically typed, but nowhere is there any guarantee that the thing you're requesting to be injected actually exists inside the DI container.If you weren't using DI, you could do the same thing any number of ways, from local instantiation all the way to having a global resource, (not saying that that would be good, only that it would be possible,) and if you tried to refer to something that didn't actually exist, the compiler would catch it. DI takes this protection away.
I still have compile time checking that I'm using my IDependencyOfMine interface properly. Even if the implementation you described is right (which I doubt, but don't have time now to go crawling around in Castle's code, might be interesting) all it does is also add a runtime check that the type being returned matches. Either way, I'm guarded from my object getting the wrong kind of thing.
I'm still not seeing the problem regarding static type checking.
-
@masonwheeler said in Should I prefer consistent design or good design?:
@benjamin-hall said in Should I prefer consistent design or good design?:
there's a lot to be said for passing in dependencies explicitly
Sometimes, when it's appropriate. A lot of times, we see people taking it way overboard and "DEPENDENCY INJECT EVERYTHING!!!" and violating encapsulation. Remember CS 101, where they taught you that a well-designed class should hide its internal implementation details rather than exposing them to the world? Many of the ways DI gets used these days fly directly in the face of this principle.
You can go too far overboard in the other direction, too. "I need to know the current user's timezone for this time conversion utility method, I know it's stored in the ASP.NET session data, let me just grab it from there." No, that need should be a declared dependency on either class or method.
-
@tharpa said in Should I prefer consistent design or good design?:
I glued thumb tacks to the bottom of the table top in front of my seat. When I started to nod off, I could press my fingers on the points.
That sounds more like @Perverted_Vixen's pastime.
-
@masonwheeler said in Should I prefer consistent design or good design?:
This. DI is a bunch of spooky-action-at-a-distance that throws a whole bunch of the benefits of static typing out the window.
TDEMSYR. DI is putting your dependencies where they're needed. Whatever it is you're talking about cannot be DI.
@masonwheeler said in Should I prefer consistent design or good design?:
It may make some things easier to code, but at the price of making it significantly harder to debug.
It generally makes it slightly harder to code, but easier to debug. Whatever you're talking about is still not dependency injection.
@masonwheeler said in Should I prefer consistent design or good design?:
Sometimes, when it's appropriate. A lot of times, we see people taking it way overboard and "DEPENDENCY INJECT EVERYTHING!!!" and violating encapsulation. Remember CS 101, where they taught you that a well-designed class should hide its internal implementation details rather than exposing them to the world? Many of the ways DI gets used these days fly directly in the face of this principle.
If it breaks encapsulation, it isn't dependency injection. What even are you talking about? If you design your classes well, injecting your dependencies manually or through a framework should be just as easy.
You don't have a clue what DI is, and are just spouting nonsense about bad IoC containers.
-
@blakeyrat said in Should I prefer consistent design or good design?:
@masonwheeler Please please please please please when talking about Dependency Injection as a concept, do not conflate it with "a horrible bloated reflection-based scary-action-at-a-distance" library that bad programmers use to implement Dependency Injection.
Mason is obviously talking about the latter.
DI itself doesn't require anything other than your fingers on your keyboard wring plain-to-see lines of code that the debugger will trace over with no problems at all.
Fair enough. But again, all too often this involves violating encapsulation and exposing internal details to the world. I don't like that side of it either.
Even the crappy, crappy DI library I was forced to use at a previous job wasn't that crappy. It'd only do its "magic" with interfaces, and kept a list of the "default" implementation of the interface. So you never got
object
.I don't think I've ever seen one that requires that you only use interfaces as your types, but sure, I can see how that could happen. Even so, this doesn't invalidate my basic point that you can ask for something that was never registered with the container and never notice until it blows up at runtime.
-
@mikehurley said in Should I prefer consistent design or good design?:
Either way, I'm guarded from my object getting the wrong kind of thing.
I'm still not seeing the problem regarding static type checking.The problem isn't just type safety, it's static correctness safety. You're not "guarded from" asking for something that doesn't exist and not realizing anything is wrong until it blows up at runtime, which is exactly what static compiler checks are supposed to prevent. It's the same class of problem as dynamic typing, just expressed in a slightly different way.
-
@masonwheeler said in Should I prefer consistent design or good design?:
Even so, this doesn't invalidate my basic point that you can ask for something that was never registered with the container and never notice until it blows up at runtime.
No, you can do that with a bad service locator or a bad IoC container that makes you manually register everything. Because those are bothg bad, and have nothing to do with DI, which is the concept of declaring what you need to function.
@masonwheeler said in Should I prefer consistent design or good design?:
Fair enough. But again, all too often this involves violating encapsulation and exposing internal details to the world. I don't like that side of it either.
Making things small and reusable is good. man.
-
@masonwheeler You should not be "requesting" a type from the container by name. You should not see the container. It should never come anywhere near your classes, beyond composing them somewhere high up. Otherwise it's not DI in the slightest: It's a service locator, the opposite of DI.
-
@magus said in Should I prefer consistent design or good design?:
@masonwheeler You should not be "requesting" a type from the container by name. You should not see the container. It should never come anywhere near your classes, beyond composing them somewhere high up. Otherwise it's not DI in the slightest: It's a service locator, the opposite of DI.
I believe the fancy pants term is "composition root".
-
@mikehurley Yes, and that's only if you use an IoC container. Which is not something you have to do.
-
@magus said in Should I prefer consistent design or good design?:
@masonwheeler You should not be "requesting" a type from the container by name. You should not see the container.
You don't need to, for this problem to exist. You just have to depend on a type that the container, wherever it exists, is expected to provide but doesn't have any means of providing registered.
It should never come anywhere near your classes, beyond composing them somewhere high up.
So you're explicitly in favor of spooky action at a distance?
-
@magus said in Should I prefer consistent design or good design?:
Making things small and reusable is good. man.
In what bizarro world does making something's public surface larger and full of external dependencies make it smaller or more reusable?
-
@masonwheeler said in Should I prefer consistent design or good design?:
You don't need to, for this problem to exist. You just have to depend on a type that the container, wherever it exists, is expected to provide but doesn't have any means of providing registered.
Say it with me: IoC containers have nothing at all to do with DI.
@masonwheeler said in Should I prefer consistent design or good design?:
So you're explicitly in favor of spooky action at a distance?
Read. I said you don't need an IoC container for DI. Stop being stupid.
-
@masonwheeler said in Should I prefer consistent design or good design?:
@magus said in Should I prefer consistent design or good design?:
@masonwheeler You should not be "requesting" a type from the container by name. You should not see the container.
You don't need to, for this problem to exist. You just have to depend on a type that the container, wherever it exists, is expected to provide but doesn't have any means of providing registered.
It should never come anywhere near your classes, beyond composing them somewhere high up.
So you're explicitly in favor of spooky action at a distance?
What's spooky about it? If you have an IoC container you resolve your composition root. Its dependencies and their dependencies (and so on) are filled in before you get that root object back. If you get weird stuff added to your objects, you set your break point where you have access to the container and look at what's registered. Kind of annoying, but not difficult.
-
@masonwheeler said in Should I prefer consistent design or good design?:
Fair enough. But again, all too often this involves violating encapsulation and exposing internal details to the world.
Like... "here's the classes this class needs to function"? Why is that a bad thing?
You're gonna have to unfold that for us I think.
@masonwheeler said in Should I prefer consistent design or good design?:
I don't think I've ever seen one that requires that you only use interfaces as your types, but sure, I can see how that could happen.
I don't know what it "required". That's how it was configured to work.
Some previous developer installed it, and the development staff as a whole agreed that we'd work to phase it out ASAP because goddamned. I don't even remember the name of it now.
@magus said in Should I prefer consistent design or good design?:
@masonwheeler You should not be "requesting" a type from the container by name. You should not see the container. It should never come anywhere near your classes, beyond composing them somewhere high up.
Until it breaks somehow and it takes you like 3 days of lost work to figure out what the fuck is going on because you can't just attach a debugger to trace through it.
-
@masonwheeler If you have a class that depends on 30 small classes, perhaps you've written it badly?
-
@magus said in Should I prefer consistent design or good design?:
@masonwheeler If you have a class that depends on 30 small classes, perhaps you've written it badly?
Hey , leave alone! Stop telling him I said 30 (or any other arbitrary large number)!
-
@blakeyrat said in Should I prefer consistent design or good design?:
Until it breaks somehow and it takes you like 3 days of lost work to figure out what the fuck is going on because you can't just attach a debugger to trace through it.
I was only saying that that's better than asking for a type from a service locator where it's needed. I did not say either was good.
-
@masonwheeler Hey mason, try reading.
-
@magus said in Should I prefer consistent design or good design?:
@masonwheeler said in Should I prefer consistent design or good design?:
You don't need to, for this problem to exist. You just have to depend on a type that the container, wherever it exists, is expected to provide but doesn't have any means of providing registered.
Say it with me: IoC containers have nothing at all to do with DI.
Since you can use IoC containers to accomplish DI, I wouldn't say that.
@masonwheeler said in Should I prefer consistent design or good design?:
So you're explicitly in favor of spooky action at a distance?
I said you don't need an IoC container for DI.
This is correct.
-
@mikehurley Yes, they are a (usually terrible) tool - they are not the same thing.
-
@magus said in Should I prefer consistent design or good design?:
@masonwheeler Hey mason, try reading.
Please refrain from conflating "ignoring inane blather" with "not reading" in the future.
All these things you're saying have, like, totally nothing at all to do with real dependency injection are exactly what people call "dependency injection" in modern parlance. It's exactly the sort of stuff that major, widely-used frameworks such as ASP.NET push on you under the name of "dependency injection." So take your No True Scotsman elsewhere, please.
-
@magus said in Should I prefer consistent design or good design?:
@mikehurley Yes, they are a (usually terrible) tool - they are not the same thing.
I guess I've never seen these IoC libs be terrible like people are saying in this thread. Or at work we lucked into picking a decent one?
Our .NET code uses Castle Windsor and it generally works pretty well.
Within the past year I used SpringMVC for a webservice and that was pretty painless too.
-
@masonwheeler No. Everyone knows what the difference between DI and IoC and specific implementations is. There are books on it, and SOLID spells it out. This is all you.
-
@magus said in Should I prefer consistent design or good design?:
SOLID
...has nothing to do with dependency injection. The D in SOLID stands for the Dependency Inversion Principle.
-
@mikehurley said in Should I prefer consistent design or good design?:
I guess I've never seen these IoC libs be terrible like people are saying in this thread. Or at work we lucked into picking a decent one?
Our .NET code uses Castle Windsor and it generally works pretty well.
Within the past year I used SpringMVC for a webservice and that was pretty painless too.
If it's luck, then we were lucky too. We used to use Ninject, and only moved away from it to StructureMap because of performance issues. Both of them were pretty good about doing what we needed most of the time, and giving helpful errors on the rare occasion when something couldn't be resolved.
-
@masonwheeler said in Should I prefer consistent design or good design?:
So you're explicitly in favor of spooky action at a distance?
Also known as encapsulation
-
@pie_flavor No, not at all.
-
@masonwheeler With a covariant method, no, it isn't
object
, it's aT
. Even my own crappy handrolled DI framework with all the weird proxies did that, years ago at what Java 6 could do for generics...
-
@magus I'd rather ask for my type from a service provider pattern, than try to trace a broken container's injection.
-
@masonwheeler said in Should I prefer consistent design or good design?:
static correctness safety
How do you like your compile times ?