https://news.ycombinator.com/item?id=4549544
I've written small Java apps --- one class only, i.e. one source file --- which needn't be any more complex, yet coworkers have said there's something uncomfortable about my code; but for some reason can't explain exactly why. I'd get comments like "wouldn't it be better if you made this (only used once and very trivial) line of code a separate function?" "could you use more classes?" (for a <100LoC script-ish thing that had almost no duplicated code nor much in the way of loops.) It's almost as if they can't get their heads around how simple something can be, so it somehow feels very wrong to them.
Then there's the extremist "premature optimisation is evil" attitude, which I think is completely misguided because more code and complexity is bad not only in terms of computer time but also programmer time --- it takes more time for programmers to design, write, read, and debug more complex code.
Many of the instances were things that we were only likely to make one example of either. I could see that argument being valid if there was a clear second implementation in mind, but an interface with only one implementing class is often just wasted time.
1. Lambdas: Lambdas are brief and to the point, the expressions reflect the terseness of `C/C++`. In many situations, you can totally do away with interface creation by just using a lambda instead. Consider all the lines of code saved in a legacy code by just using lambdas in code!
2. Default methods: Again, `Interface patching` is a curse that a lot of Java projects suffer from. Requirements change slightly and what your typical enterprise Java dev does is add patches upon patches and layers upon layers in an interface. With default methods, you can just add default methods to your existing interfaces without affecting the ABI of your app. Again, tons of redundant code saved here.
3. Type annotations: Again, your typical Java devs have to resort to ugly hacks to place any constraints on the object properties. With type annotations, all you have to do is:
@NonNull String str;
And lo and behold! The variable `str` cannot be assigned any `null` value. So, lots of validation code being saved here again.To the best of my knowledge, none of the above features exist in the `C#` language (yet), so I take it as a Java's edge over C#.
You also asked "I don't know what major project you are talking which uses these features". As far as I have seen, and I've seen a fair amount, any and every non-trivial c# program (and many of the trivial ones too) use lambdas. Even if it's just in the small-scoped, rote use e.g. of
var matches = someList.Where(item => item.x > y);
Technically that counts as "using a lambda". Don't knock it for being trivial, it's a gateway drug ;)I never felt like interface patching was a problem for me, may be because I'm more of a FP than an OOP programer. Type annotations do sound interesting.
I have seen a little of the interface vs function conversions in modern Java, and it seems a decent enough small convenient feature in the context of Java, but it doesn't feel like a big win that I would cry out for in C# code.
Defaulting to not-null and immutable values is a trend in several recent languages, e.g. from Swift and Rust to F#. This IMHO looks like a good step forward and I welcome it, however it's hard to see how this would be comprehensively added into existing languages like Java or C# with an existing legacy of not working that way. Opting into Not-null in c# has been talked about many times (1) in various forms, even implemented using attributes (similar enough to type annotations). (2)
1) http://twistedoakstudios.com/blog/Post330_non-nullable-types...
2) http://stackoverflow.com/questions/792531/c-how-to-implement...
I'd get comments like "wouldn't it be better if you made this (only used
once and very trivial) line of code a separate function?" "could you use
more classes?"
The trouble is that there is a cargo-cult belief in unit testing in the Java (and .Net) worlds. Everything must have a full suite of unit tests, even if it's a one-off script. Therefore, in order to enable the mock objects needed for unit testing, you can't have "just" a class. You have to have an interface, which that class then implements. If you have any non-trivial piece of functionality, it must be encapsulated into its own method, otherwise, how are you going to test it in isolation.My belief is that it all stems from the fact that Java doesn't have a REPL, so programmers use "test-driven development" to get the same level of interactivity from their code.
What library was this?
Pretty interesting when you think about it - java hasn't moved that conceptually far from C preprocessor statements.
It was built around lambdas in Smalltalk long before that.
Simula (about 4 years older) had a more typical class-based OO with vtables and a proper subtyping already. Long before any C.
It's not the same thing though. REPL gives you fast feedback once. Unit tests you build by following TDD are there to stay.
Once a program has been in production for a while and its use cases are more well defined it becomes easier for a single person to swoop in and rewrite it in a far more conscice way. In most of these cases I think the language is pretty irrelevant in terms of how much can be culled.
> The iterative bloat during development can happen for so many reasons. (Numerous changes in requirements late in the project,
... and people not cleaning their mess up afterwards;
> many developers working on numerous modules,
... without proper coordination, or, without the drive to keep things clean (i.e. proper manners);
> design for flexibility and expansion that are never used,
... and never cleaned up, i.e. left there to rot by the people who put them there;
> going too far down a path that turns out to be more work than an alternative)
... and not cleaning up.
> Once a program has been in production for a while and its use cases are more well defined it becomes easier for a single person to swoop in and rewrite it in a far more conscice way.
I think it's always easier for the guy/gal who wrote something to clean it up than for the next guy/gal, who doesn't know the code or its history as well.
Really, this is all just people not cleaning up (or being allowed to clean up) after themselves.
Now if we could all just be nice to each other and hold hands..
Most of the time there isn't any budget left after they have a functional app for cleaning things up. Despite impassioned hand wringing the client will place value on things they can see over things they can't. So any budget left over will almost invariably be directed toward additional features and not on proper engineering.
Sad but true.
Speaking to your points there's also the problem of not being able to see the forest through the trees for developers who have been with a project from the beginning. So large refactoring endeavors can be less obvious to them. They may also have fatigue and be less willing to rewrite everything. This combined with budget pressure is all it takes for the bloat to stick.
Yes, I know, tech debt is a thing and those that don't pay attention to the engineers will soon find themselves with a shitty code base that they can't sell.
It can be really, really hard to persuade a management board that you need to take a bunch of their expensive techs to not write new features that will improve saleability, but instead rewrite the code-base (that they're still depreciating), for no net gain (and considerable risk) except to maybe reduce the support overhead and make future developments easier and cheaper.
I'm a fan of the first approach. Building it right is the most efficient way to build, with the overall lowest amount of effort required. It doesn't mean gold-plating, rather the opposite: keeping things light, elegant and minimal. Anything that can't be built cleanly isn't built at all (yet), or at the very worst it is mocked with a fixed data implementation, which suffices for demos but not for shipping.
However, the problem with building it right is that you have to learn all the wrong ways to build before you learn how to stick to the narrow path of clean code. I wish we could do brain dumps from old hands to young wolves and not see the same mistakes repeated by every generation of programmers.
"building it right from the start" sounds great. I've never worked on a project where people did not honestly try to do it this way. Lets say that for once you have a clear set of unambiguous requirements (a rarity) when starting in. I like to start with the database first. I'll design a nice normalized database and start building the website from there. If the requirements are fleshed out enough you'll already have a map of what pages ought to do what.
Once you get to the point where you can have a customer try out a few pages you get some "feedback" which is actually a change in the requirements. You can't hit them with a change request for every little thing at first so you comply. Maybe it's just a small change turning a one to many into a many to many relation for example, or moving a few fields from one table to another. Soon you have 30 or so similar "small tweaks" even if you hit them with a change request every time, they don't all come in at once giving you a good opportunity to re-evaluate the schema/application as a whole, it's always viewed as a singular "small" change so re-factoring the whole thing isn't really an option. The more changes that come through the more your elegant implementation becomes a series of hacks.
Soon they may start complaining asking why their "small" changes are taking so long to fix. This adds further budget pressure.
The problem is, it's not very clear to people down in the weeds when implementing the small changes if it's a hack or not. I think this is something that takes experience. So even when you have cleanup be a blocking requirement, taken one at a time it might seem fine and no large refactor is obvious until it becomes too large of a task to lump in a typical change request.
This is an especially easy trap to fall into for people who have been toiling away at the current design. They will have a blind spot/affinity for keeping as much of their implementation as possible and resist an overhaul.
If you have an iron will and a steely ice cold 1000 yard stare from 10 years of war in enterprise app development it's easier for us to kill code because we don't get attached to it as easily.
Be careful not to fall into the dark side though, (the flip side of those 10 years can instil a cold iron heart who stares right through the hacks and stops trying to argue with the client about doing things properly. In this scenario you just do as you are told and stop caring about the overall state of well-being of the codebase.)
The real art to web dev consulting is being able to build a flexible enough design to withstand these sorts of changes and still remain elegant and well thought out. It also takes the experience to know the difference between a hack and a proper change and the will power to do the right thing. It's really hard to do and it a good "gut" feeling for how a particular thing will actually be used, what a customer really means when they say something etc. I'm not sure that it's something that can be taught. It leads right back to your brain dump wish.
Cheers
This is where it gets tough. I happen to have http://www.colorforth.com/POL.htm [1] open, a portion of which illustrates the other side of this fence (emphasis mine):
> Do not put code in your program that * might * be used. Do not leave hooks on which you can hang extensions. The things you might want to do are infinite; that means that each one has 0 probability of realization. If you need an extension later, you can code it later - and probably do a better job than if you did it now. And if someone else adds the extension, will they notice the hooks you left? Will you document that aspect of your program?
Obviously all of this must be taken in moderation, of course. But it hints at the problem I'm trying to describe: the design needs to be flexible, but scoped to the problem space you're working within. Bamboo is flexible, and usefully so; try and generalize that flexibility to the fullest extent and you'll end up with goo which will expand to fill the problem space entirely and then subsequently accomplish nothing.
So, it's about figuring out where flexibility will be most critical (eg, for a part of the architecture that will necessarily be "locked in" with a lot of other components or mental models depending on it) and where you should be able to get away with rigidity, either because it'll be easy to redesign (few dependencies / easy to conceptualize and subsequently retool) or because even Revision #31781 won't need you to change that bit.
...I think I just found myself at the brain-dump problem too. Haha
I'm also curious... I've known about the "kill code" thing for a while. I haven't really gotten into coding especially seriously as yet (lots of analysis, but nil implementation), and it's hard to contemplate the idea of doing this, even for situations where I know I definitely need to, like iterating on properly implementing something I've never explored before. How does one acquire the "iron will" mindset you talked about?
(I ask this as I've recently discovered my lack of get-up-and-go is due to a wonky/underpowered thyroid (giving me chronically low motivation); now that I know what's wrong I'm working on fixing it, but I suspect the anxiety that's developed over time will not go away without some mental effort as well. You could say I'm a bit of a worst-case scenario when it comes to juggling multi-domain mental task sequences generally speaking.)
[1]: Search http://www.colorforth.com/blog.htm for "Problem-Oriented" to find the hyperlink to the mentioned URL. There are many interesting links to other places on this page, which I don't think are mentioned anywhere else on the site. (Translation: there's no sitemap, category system, or menu; you have to dig.)
First of all, you need to be comfortable using source control to the point where you can kill a bunch of code then find it and merge it back in months and hundreds of revisions later.
Experience and education will give you the ability to recognize when you have bloat.
You may need lots of bad experiences and a memory long enough to remember the pain of dealing with bloat and cruft.
The will power portion is what makes you do the right thing when you do recognize it, if you have a strong will you won't need to learn the hard way repeatedly. Coffee helps with this, if you feel yourself slacking, drink more coffee drink stronger coffee. If you still can't do it, look into getting an ADHD perscription (i've got one).
Once a piece of functionality gets established in production, over time the knowledge of the original requirements and the implementation is lost. The consequence is that nobody knows why the code does what it does (and usually it does something important) and due to it working fine in production there is strong pushback to making changes.
I definitely agree with your ethos though. I think there's a lot to be said for having some buffer between functionally complete code and a hard ship date so that you can leave things in a state that don't make you or someone else feel bad about things later.
Indeed, I once had the pleasure of replacing a BizTalk server with 50 lines of C#. It wasn't that the BizTalk server wasn't a bad idea. The project was for a big harbour, which have documents, cargo manifests, import papers and a ton of other stuff flying back and forth. The idea of having a central hub for data exchange and data conversion wasn't actually that fare fetched. It's just that the only part that was ever implemented was a the upload of a small 5-10 line xml file to the maritime administration, regarding the departure of ships.
After three years of running a BizTalk server, for that one purpose, and with no-one fully understanding Biztalk, we just replaced it with a custom Windows service.
If anything the takeaway is that you projects should be revisited once in a while for clean up.
After the dust settles you can see what it's actually used for. And sure, its not like the other use cases were bad or wrong, its just not how it ended up.
It's possible that the other use cases never got traction because of some longest yard effort required on the client's end to implement a workflow. But even that low of a bar ended up being too high.