4 ms·
I feel that most 100K line programs could be rewritten with just 10K lines and end up being more reliable. Feature creep is responsible for some of the code bl
by jongjong 3y ago
I feel that most 100K line programs could be rewritten with just 10K lines and end up being more reliable.
Feature creep is responsible for some of the code bloat but I can guarantee from experience that, in the vast majority of projects, you could keep all the features and still cut the code to at least 1/10th of its size.
I think the reason for this is because developers who focus on development speed do so at the expense of succinctness. The more foresight you have when you're writing code, the fewer lines you will end up with. Unfortunately, developing that foresight requires time spent not coding; it means choosing the best option out of all viable alternatives. When developers are pressed for time, even if LOC is not used as a metric to judge them, they will not have the time to look ahead in the near future to minimize the lines of code.
Workarounds tend to require a lot of lines. When code is rushed, it ends up getting littered with workarounds which require additional checks, additional tests, etc... A bad foundation with sub-optimal abstractions can force developers' hands and lead to even more bad code being produced on top.
Before I start working on a feature, I simulate how it's going to work in my head and try to identify all the hurdles and alternatives; sometimes several levels down in the hypothetical component/module hierarchy. I do brainstorms, draw diagrams and make lists of pros-and-cons. I use as many visual aids as I can get. I play devil's advocate with my own ideas until I'm at my mental limit and I cannot visualize the solution (and requirements) in any more detail and cannot identify any other hurdles. It actually feels like playing chess. You need a strong understanding of your tools and environment to be able to do do this kind of adversarial brainstorming and most importantly, you need time. Especially in the early stages of the project. The further along you are in the project, the less foresight you need.
- antaviana 3y agoReminds me of Mark Twain's quote "I didn't have time to write you a short letter, so I wrote you a long one."
- piva00 3y agoSure they could but we don't work in a vacuum where succinct code is the product, the business need is the product, with that comes a lot of complexity that only increases exponentially with the size of the organisation. I can avoid to rush code when working in something internal to my team, the moment I need to work on something that is part of a much larger project then all the issues with synchronisation/coordination crop up, and we don't have the time to spare anymore to go through a 30+ teams/5 orgs interdependencies and play chess with all the permutations of interactions between all systems to arrive at an optimal solution which requires the least code possible. I can work to approximate to that as much as possible but it's simply impossible in a large project spanning many teams, systems and organisations inside the company. At my job we do use RFCs a lot to do pros/cons analysis, play devil's advocate with each other, communicate with others our ideas and check if the proposals make sense in a larger scope, still business needs trump everything else. If my proposal is "it will take 6 months to do this properly" vs "we can make it work in 2 months if we adapt the existing thing to do thing + A" then the shorter timeline will always be chosen, and as a SWE it's my job to deliver the business value, not to write the perfect code I'd like to. There's never enough time to write code as we wish we could, it sucks, I'd much rather not have to implement hacks that I know will create future pain to myself and to others but I'm not paid to do that, what is under my control is to try to create as little future pain as possible, documenting all the pitfalls, shortcomings, and issues I can already foresee when a hack-ish feature is implemented. Yes, most experienced/smart SWEs will know that a lot of software could be written with much less code, when you are experienced you also know that it's just a fantasy, reality constraints shape software and the shape it takes is of a monstrosity, in the end it's just a spin-off variation of Conway's Law: we ship code that reflects business constraints and timelines, not engineering ones.
- kot-behemoth 3y ago> Before I start working on a feature, I simulate how it's going to work in my head and try to identify all the hurdles and alternatives; sometimes several levels down in the hypothetical component/module hierarchy. I do brainstorms, draw diagrams and make lists of pros-and-cons. I use as many visual aids as I can get. Remarkably, this is how 37Signal's Shape Up (https://basecamp.com/shapeup https://basecamp.com/shapeup) encourages defining feature work prior to building. In particular, the concepts of Rabbit Hole (as explored with senior developers prior to coding), Breadboarding and Fat Marker Sketches (having a high-level but end-to-end map of the feature) are almost identical to what you're describing. I found this approach both intuitive in my personal development work, and as a tech lead for lean teams. Funnily, quite a few people really struggle with the concept of "thinking through the feature end-to-end", and not just "let's start with one piece and then figure it out". It's great to do development in small chunks with unknowns, but we still need to know what we are all trying to achieve! (not affiliated with Shape Up / Basecamp, I just feel Jira leads to hugely suboptimal and waterfall-y processes).
- uaeu321321 3y ago[flagged]
- jcarrano 3y agoI agree 150%. I often find myself, after finishing a "big" application, thinking "well, this must have been thousands of lines". Then I run sloccount or wc and I'm always surprised with how few lines of code I have. The thing is, as you say, I spend more time thinking than writing.
- bluGill 3y agoIf you removed from a complex program any feature that was used by less than 2% of the users the program would be much smaller and simpler. However you also lose each 2%. In many cases every user uses a different subset of what your program does, and so the end result is no users at all because your program is useless.
- vidarh 3y agoYes, but part of the argument was that you can often cut drastically without cutting features when you take the time to understand the problem properly. Sometimes everyone actually does use genuinely different features, but more often there are different ways of solving the problem that will still be more concise even if you keep everything. To take a somewhat concrete problem from a past job: We had an agency do a bunch of work on features I didn't have time to work on. Being an agency used to be brought in to add a feature here and a feature there, they worked in a way that allowed for fast individual features, but that slowed us down overall and bloated the code when we had them in to do a bunch of work: They'd manually write each screen. We had many dozens of models that needed CRUD stuff. They did perfectly fine work, and had we only wanted to expose a handful of classes, I'd let them do that. When I got the time to review what they were doing and realised how much near-duplication they caused, I instead wrote a piece of code that introspected the database model, layered on annotations from our models including access control and additional type information, and spit out a bunch of JSON the front-end consumed to produce a generic CRUD interface to all of the tables. They first objected that there were too many things that were different between each of the screens. They were right there were many differences, but there were more similarities, and we could easily accommodate allowing them to override that. They went from building new screens for everything, to picking a generic, automatically generated screen that was sub-optimal, configuring views, and writing new components to view various types in different ways. Each new component often made it trivial to make multiple screens better with minimal effort. We didn't remove a single feature. They could still override the views whenever necessary with custom code. In fact, we added access to dozens of models that people had to ask someone to run SQL queries to access before, so the overall system is far more feature-ful. But the average amount of code needed per model is a small fraction of what it was before. This kind of thing is common. People keep writing boilerplate or writing to abstractions that cause them to write far more code than necessary without stopping to think about how to simplify, or perhaps more often without having the power to decide to do something about it. A lot of the time the simplifications also aren't obvious until you've spent some time being verbose and recognising the patterns that will allow you to be concise without sacrificing functionality.
- rcme 3y agoFirst, I think we should use expression count instead of pure LOC because many styles add white space but keep the expression the same. I don’t consider one style “more terse” than another. e.g. a.map(…).reduce(…).join(…) a.map(…) .reduce(…) .join(…) If you can accept that expressions are a better metric for “terseness” then I will categorically say your statement is pretty easy to disprove. Essentially you’re saying that every 10 expressions can be rewritten as a single expression. I don’t believe that to be true, based on a cursory glance of some code bases I’m familiar with.
- taeric 3y agoMost any metric can be gamed. At large, though, I'd wager that the noise from these scattered through a codebase are minimal to the point. That said, I do think I agree that tooling is good enough now that you can probably try both counts and see what can be seen. With the idea that these are not precise numbers into complexity, but directional concerns.
- ssivark 3y agoThe spirit of the claim is to reduce the number of expressions by 10x while only sacrificing the “unnecessarily complicated” functionality. It is a question of system design that demands judgement, and not just a raw code length compression. It’s about preventing large (and poorly thought out) projects from being worked on in the first place, before they need to be scrapped or circumvented.
- nijave 3y agoCode generation is also pretty prevalent, some languages/ecosystems more than others
- wheels 3y agoMay I venture that you're probably early in your career? There's almost always things that can be redesigned to be better and smaller if one has a better understanding of total scope from the beginning, but there's equally as much discovery that the reason things seemed unnecessarily complicated was a lack of understanding of the complexity, and that the new rewrite eventually reintroduces much of it as it's used in production.
- convolvatron 3y agothis isn't always true. more than once I've taken a large codebase, whacked it down to 10% of its original size, without losing any features and gaining quite a bit of performance. smallest-change maintenance by lots of people just introduces cruft by its nature. not suggesting that doing that rewrite is usually a good idea...but I disagree that all that stuff always represents anything fundamental
- wheels 3y agoI didn't say it always does. I said it's about 50/50 on whether the cruft is from actual complexity vs. bitrot.
- hansvm 3y agoIME, long-term multi-author software projects tend to accumulate cruft in a way that doesn't require drastic re-writes (as opposed to, e.g., an architectural mismatch which if solved would yield the desired 10x improvement but poses a variety of huge risks, both in actually completing it and how bad things are if you fail). 1. With each feature request, pick one thing to improve (a bug, tests testing mocks instead of code, duplicate classes, inconsistent error handling strategies, complicated logic, ....). 2. First make the improvement. Propagate the beneficial impacts throughout the codebase (remove methods that only existed to support the mock, remove utility methods that only existed to support those, remove tests for the utility methods, remove duplicate tests on the previously duplicated classes, remove code duplication elsewhere that bifurcated due to the duplicated classes, change your Result<T> return type to just a T type because your sanity check was wrong and the method can't actually fail, you no longer need to pattern match (or catch exceptions) on that result type because failure isn't an option, ....). 3. Then implement the feature. The nearby code was just improved, so this is a bit easier than it would have been. 4. Repeat ad infinitum. After doing this consistently for a little while, the 10x reduction in code happens on its own, and it's faster to implement new features _and_ fix the little bugaboos than it was to just implement a feature starting out. Your code is more stable, your builds are faster, your code is faster, your tests are faster, your tests catch real bugs, feature velocity goes up, you don't accidentally expose a race condition in your driver because of dumb concurrent complexity in the application, and on and on. Then, if an architectural mismatch exists, the code is in an understandable state. It's leaps and bounds easier to re-write a 10k project than a 100k project. YMMV. Not all code is that bad, but a significant fraction of older projects turn out that way eventually.
- NoMoreNicksLeft 3y ago> you could keep all the features and still cut the code to at least 1/10th of its size. I do not disagree. I feel like, in some cases that might even be the minimum reduction. In some cases it might be more like 1/20th. But does that cut require twice as many man-hours, or does it require x40 man-hours? More? Whatever the actual answer, I do not think it cheap. I don't even know how to guess how much added effort and time it requires. In many cases and companies, it might even require hiring highly sought-after engineers... in-house talent simply isn't up to it.
- convolvatron 3y agoI don't think its a question of time spent. it is certainly a matter of experience
- pixl97 3y agoExperience it turns out, is massively expensive.
- hinkley 3y agoMy rule of thumb is that most devs can only really be “responsible” for around 25k lines of code (SLOC) at a time. Almost to a man, everyone who disagrees with me by more than a few percent turns out to be cranking out buggy code. If you’re not on top of the bugs you’ve diluted responsibility. Your affect on the bus number situation is compromised. The 25k axiom is why I’m always trying to prioritize between concision and readability. It’s easier to hand off readable code, it’s easier to manage concise code. Concise code that is not readable is terse, so there’s a balancing act to be managed there. I won’t say they’re at odds, but they can complicate each other.
- darkerside 3y agoI know that 10% of my marketing spend gets me 90% of the benefit. The problem is, I don't know which 10%.
- tivert 3y ago> I think the reason for this is because developers who focus on development speed do so at the expense of succinctness. The more foresight you have when you're writing code, the fewer lines you will end up with. Not necessarily. 1. The process you describe works pretty well for code you wrote yourself. However, once the original developers are gone, any understanding, foresight and future plans they had (beyond the coarsest, most high level ones) basically got burned in a fire. That means a lot of minimum-effort jiu-jitsu solutions aren't available anymore, and you'll have a lot more kludges and re-implementations of things that may already be there (but forgotten). 2. Few developers have the luxury of infinite time, and succinct code is rarely a business priority. That means you pretty much never can go all-in for succinctness, which would frequently require large scale refactors to achieve. The end result is that, inevitably, over time, features and bug fixes will get grafted onto foundations that weren't ever meant to support them.
- GuB-42 3y agoThe problem is that reality is complex, lots of special cases. Dates are a well known example, with lead days, leap seconds, time zones, DST, etc... So is localization. You may have the perfect code for your use case, with the right level of abstraction, until reality kicks in. For example, let's say you are writing a device driver, each device identifies itself with a unique identifiers, fine, until one day, you get different devices with the same id and you need to issue a special command to differentiate between them. And then, the next device receive commands in little endian order, even though all the other ones were big endian. And then another device sometimes silently fails to execute a command, you then need to a bit of time and check that it was actually taken into account and retry if it wasn't. Etc... You can't plan for all that, if you try, it will only make things worse. All that will invariably result in ugly code, it is not bad design, it is just that reality itself is ugly. If you try to start over, if you are really really good, you can reduce it to something nice until the next dose of reality. But more likely, you will make things even worse. Believe me, if you think you are good, try rewriting a legacy app that is used in production in a way that doesn't break production, I predict a humbling experience.