17 ms·
First 1-3 years of coding, I just coded to get sht done. I got a lot of sht done. Next 4-8 years, I started getting cute with it and applied all kinds of desig
by jaequery 7y ago
First 1-3 years of coding, I just coded to get sht done. I got a lot of sht done.
Next 4-8 years, I started getting cute with it and applied all kinds of design patterns, Factory, Abstractions, DI, Facade, Singleton you name it. It looked cute and felt good when it all worked but it was a juggling act. There was usually like 2-3 files to touch just to do one thing. UserFactory, UserService, UserModel, User, you get the idea. It got to a point coding now felt like a burden and I started getting allergic reaction to any projects that had more than 50 files.
Next 4-5 years, I made it a mission to only code in a pragmatic, minimalistic way. Best decision I ever made, this have been the most productive time of my career. I don’t look back and never going back again. A simple require and requireAll with basic OOP is all I need on most cases. Most of my project now have less than 10 “core” files minus the standard views/routes/etc. I enjoy just working on code now it makes me happy and also any devs who joins loves it too as they get it right away. I code almost exclusively in Sinatra now btw. Express is great too but I think the ecosystem isn’t there yet for developer happiness.
Keeping code simple is not easy. It takes a lot of trials and errors to know what works and what doesn’t. I realize I code a lot slower now than in the past and that I write much fewer lines of code. It’s both good and bad because sometimes I’d even spend hours just trying to properly name a variable. But I believe this pays off at the end.
You just can’t best simplicity.
- winrid 7y agoYou can break things up without overdesign right? I think you should do what is easiest most of the time. However, that is hard to measure. Easiest now, or when you need to finish this and move onto the next thing without spending two more sprints fixing bugs? I prefer small/reasonably sized components because I can easily cover them in unit tests and sleep easier at night. I built a survey builder at one company (think mix of survey monkey/qualtrics) and that is probably over 100 files. But the codebase is straightforward and simple (no complicated inheritance, one tree data structure for pathing, lots of code reuse)...
- JMTQp8lwXL 7y agoBreaking things up is, by definition, design. Having meaningfully enforced boundaries between subsystems, that collectively add up to forming one larger project, is applying the concept of design.
- lmm 7y agoYou can define it that way, but in that case my position would be: devoting time and effort to design leads to worse design than not doing so.
- JMTQp8lwXL 7y agoIt depends on how carefully you've considered requirements before you begin coding. Sometimes when I start coding, it triggers the realization that I need to change the design, because I failed to deeply consider something well enough. When you're designing (paper, pen, boxes, arrows, no code), it can be easy to hand-wave or fail to consider real problems you might encounter.
- lmm 7y agoIndeed. I'd argue that this is so common that it is better to begin coding first, and allow any "design" to emerge, rather than trying to design before coding. Perhaps with extreme care one could achieve a decent design up front, but it would surely take much more effort than simply being willing to refactor as one goes.
- TeMPOraL 7y agoI've learned to prefer doing both. I start with spending enough time with pen and paper/whiteboard to get a coherent picture of what I want to build and how, and then I start coding it. I flip between "design" and "code" whenever I start to feel that current activity is getting hard. Getting increasingly confused as to what functions to write, how, and where to put them? I switch to design phase and sort it out at a higher level. Starting to feel I'm losing touch with the ground, or that I'm not able to figure out which design option is better? I go back to code and write what I can until the code itself reveals answers. In essence, I see creating software as a dance between higher-level, structural concerns and low-level, immediate needs. Since one mode solves the problems found in the other, I try to be working in whichever mode I can make progress most effectively, and flip as soon as that changes.
- GuiA 7y agoWell yes. It all sounds all so easy when you put it like that. The problem is that, in my experience at least, you can’t just teach junior engineers how to go straight to phase 3. You have to go through phase 1 and 2 to really develop a sense for what makes a solid, streamlined design. Some never get there - either because they become set in their ways early, or because they work in organizations where the wrong kind of thing is encouraged. Some get there faster - because they’ve worked with mentors or in codebases that accelerated their learning. But like with any craft, you have to put in the hours and the mistakes. (Yes, there are John Carmacks in the world who go through all those steps within 18 months when they are 12, but they are 0.0001% of the programming population)
- taneq 7y agoCarmack is actually a good example of professional development. A lot of what he talks about is how his style is evolving and the pros and cons of different approaches.
- GuiA 7y agoYes, that’s a good addendum. Like any other craft, even the masters have always more to learn. As they say, it is only when you get your black belt that you are really ready to start learning karate.
- userbinator 7y agoI think the state of CS education is actually responsible for a lot of overabstraction; students are taught early to worship abstraction (because of all the buzzwordy benefits it allegedly brings) and apply it liberally, and not taught when not to do it. A good way to counter that might be to get them to read early opensource code (e.g. first few versions of UNIX, some BSD stuff, etc.), which I think is mostly an example of "abstraction done right".
- GuiA 7y agoYes, curricula tend to focus on writing code over reading code, which is a shame. Looking at existing code, extending it, refactoring it, etc would ideally be something that students do as much as writing things from scratch.
- james_s_tayler 7y agoOut of curiosity what kind of projects are that small? I guess I have hobby projects that are that small, but all my professional work is large, enterprise systems that wouldn't fit in 10 files if they tried. Makes sense when things are so small to only use what you need. Sounds like you made a reasonable decision for the kinds of things you work on. But when you get past a certain size actual architecture becomes very beneficial. Of course it's also possible to have a massive enterprise system without any architecture. Believe me it's not very fun.
- StreamBright 7y agoNot OP but couldn't you split up you project to smaller projects? >> But when you get past a certain size actual architecture becomes very beneficial. This article and only rejects the convoluted architecture approach with design patterns and suggests that you can come up with your own design without using these. It is not arguing that there is no need for architecture at all.
- james_s_tayler 7y ago>Not OP but couldn't you split up you project to smaller projects? My own personal stuff I can do whatever I like with, work stuff I don't have much say over. Could they be split up? Oh, yes. Does it need to be 1 million lines of code? No... I think it could probably be about 1/5th to maybe even 1/10th of that. But still, even if it's split into microservices or just smaller modules, the aggregate lines of code required to solve the entire business problem is still a great deal larger than will fit into 10 well crafted files. Was just curious the nature of the work that naturally fits into that size. >This article and only rejects the convoluted architecture approach with design patterns and suggests that you can come up with your own design without using these. It is not arguing that there is no need for architecture at all. I think this article is great. I took this article to be advocating for taking a good hard look at the problem at hand and really nutting out a solution that fits it well. It doesn't reject design patters, per se, it rejects not properly thinking things through.
- 7y ago
- JMTQp8lwXL 7y agoOver my career, I've worked with engineers that like to over-engineer and under-engineer. The over-engineered code looked like russian dolls: had many layers to it, and some of the abstractions offered no value. That can make onboarding to such code unnecessarily complex. On the other hand, under-engineered code made very little of use of even simple data structures or algorithms. I like to call it "chicken scratch" code. I find it tends to be brittle, and it fails the longevity test: you end up frequently having to touch everything, and be aware of the entire state of the system at once, due to a lack of functional style. There are few enforced boundaries between subsystems in this type of code. Like most things, moderation is key. I only introduce abstractions when there is a meaningful value-add to doing so. This is somewhat subjective, but there is a right level of application of design patterns. Not too much, nor too little.
- tarsinge 7y agoI think you can under engineer and still use a pragmatic functional style. Also yes when it’s under engineered you usually have to touch multiple parts to make a change, but in the end it usually take less time (testing for bugs included since the code is simpler). Edit: I don’t say under engineering is perfect, but compared to an over engineered code base I prefer the first for maintainability.
- JMTQp8lwXL 7y agoIn theory, I agree with you, but in practice, I rarely see under-engineered code that is functional in style. My conjecture is that people under-engineering aren't even aware of the concepts of imperative vs. functional style. The more we can think of code as existing in a sandbox that knows nothing about the surrounding system it's in, the better. Because when you need to change that code, you don't need to juggle comprehension of the rest of the system as you edit that code. It makes reasoning about the code much simpler. This is why I agree -- code can be pragmatic and functional. But if you tend to practice having code existing in little sandboxes, you are essentially practicing design. This might be semantics, but now you have sub-systems with well-defined borders that interact with other sub-systems in proscribed, expected ways. I would consider that, to some degree, the application of design patterns and architectural concepts -- even if it's only the inevitable consequence of writing pragmatic, functional code.
- tarsinge 7y agoThis mirror my experience too. I think at its core the issue is that code duplication is irrationally seen as a bad thing. But from my recent experience of the last few years with ultra minimalists approach making a change to a non abstracted code is so much faster. Yes it’s boring and feel unsophisticated, but when you only have flat functions vs an architecture tightly coupled to a business process, it’s a matter of hours vs days/weeks. In short I would add to the title “because reusability is overrated”. Especially when the trade off is complexity.
- JMTQp8lwXL 7y agoI've seen code duplication fail me. In my current role, any time we want to spin up a new Node.js process, we "fork" an existing scaffold project. (Some of the repositories are actual forks; many more just copied the scaffold code and init'ed a new git repo). Whenever anything changes to the scaffold, there's no easy way to percolate those changes down the forks (then they all have to be versioned and deployed). It leads to a lot of copy-pasting and busy work that could instead be spent solving more pertinent company problems. Nobody ever considered how, once you end up with a growing X number of copies of what is nearly identical code, how to manage it. It's an increasingly unscalable problem for us. It would be 100% more efficient if all the development happened in a monolith that kept clear lines between the projects that exist as "forks".
- gmueckl 7y agoThere are also cases where code duplication is better than the alternative. Sometimes, you end up writing the same pattern (often a hand full of lines) over and over again in different modules and these copies can't easily be moved to a common place. In this case, it is likely be better to accept the repetition than to mess with high level architecture to worship the gods of DRY. Slavishly following DRY is often a cause for overengineering a system into a tar pit.
- TeMPOraL 7y agoDRY is unfortunately commonly misunderstood as a principle of avoiding repeating the same code patterns, whereas it's beneficial effects are entirely about avoiding creating repeated copies of a single concept. There are many ways to avoid or mitigate consequences of copying concepts in your system, not all of them involving abstracting similarly-looking code into a method. Sometimes, a bunch of code repetition shouldn't be DRYed, because they're different concepts, just looking the same. Your language most likely doesn't have means for necessary structural abstraction - Lisp-style macros. If it has, you can sometimes address these cases cleanly and efficiently with a macro by correctly noticing that the concepts are in fact related, they're instance of a same structure or behavior (much like a book on physics and a book on literature are unrelated if you look at the topic, but related if you look at the structure). And sometimes the best thing to do is to mitigate the consequences - through a quick unit test that fails when you change one thing without changing the other, or even a note in comments. "These two things are related, but we're not enforcing it." Or, "these two things are unrelated, even though they look the same".
- dgellow 7y agoWhat about tests? In my experience simple code without abstractions often becomes a pain to write tests for. For example that’s one of the main reason I see to use some form of Dependency Injection and other indirections, even if in practice you have only two implementation of each dependencies (once in your tests, once the real one).
- TeMPOraL 7y agoBasic dependency injection is just functional style - code getting its dependencies as arguments. I feel it's often actually simpler than having code manage its own dependencies. I didn't think that until recently, though, because my primary exposure was always bloated Enterprise Java DI frameworks written in pre-Java 8 style. I'm not saying the frameworks were bad per se, just that the amount of incidental bloat prevented me from understanding the core insights of "dependency injection".
- dgellow 7y agoI gave DI as an example, but other types of abstractions can also make an implementation more complex (i.e: less simple) but easier (or just make it possible) to test, which can be quite important (dependending on the context of course). Now for DI being a functional programming principle, I don't know, I guess you could argue for this. I personally learned it as a way to satisfy the "D" from the SOLID principles, so in a context related to object oriented programming. But there is always some level of overlap between paradigms.
- TeMPOraL 7y agoDepending how you look at it, you could argue that classical approach to SOLID's dependency inversion is an amalgamation of two separate concerns - dependency injection for ensuring that neither "higher" nor "lower" level depend on the other directly, plus a type system restricting what operations are available to both. There are so many ways to look at and reason about the problem of structuring programs that pretty much every year I discover a new perspective on an old thing that blows my mind. Now my enlightening moment about dependency injection was this: it's literally as simple as passing an argument to a function. In a functional approach, you may be passing lots of values and closures expressing dependencies with surgical precision, in an object-oriented imperative approach you might pass an instance conforming to an interface just once. But it's the same concept.
- StreamBright 7y agoExactly. I never got into OOP design patterns and my co-workers could not convince me this is a good idea. I thought for a while that I am crazy but then I got to know Erlang and Clojure. Joe and Rich set me straight on software design. >> Keeping code simple is not easy. It takes a lot of trials and errors to know what works and what doesn’t. Refactoring helps. I usually achieve 20-40% reduction with the first refactoring.
- jacquesm 7y agoEven OOP has its place. I once wrote a simulation of a physical system with a lot of moving parts and OOP made the problem tractable and the code readable and maintainable. I don't think any other style of coding would have had such an enlightening effect on that particular problem.
- StreamBright 7y agoTotally. For me just AbstractUserGeneratorFactory does not make sense. You can have a look here to get examples: https://docs.spring.io/spring/docs/3.0.x/javadoc-api/allclasses-noframe.html https://docs.spring.io/spring/docs/3.0.x/javadoc-api/allclas...
- james_s_tayler 7y agoIt's actually a pretty incredible framework. While it might just look like word soup from that particular vantage point I'd venture to say that Spring Framework is one of the most successful examples of OOP in history. It has massive adoption because of its sheer flexibility which all of those abstract generator factories give it. Granted, I think Ruby on Rails is also an amazing project. For slightly different reasons, but then again, they're different use cases serving different paradigms and different groups of people. It isn't the paradigm that makes something good or bad or better or worse. It's the quality of the implementation.
- StreamBright 7y ago
- galaxyLogic 7y ago>started getting cute with it and applied all kinds of design patterns Even though there are books about design patterns, taking such a book and trying to "apply" its patterns is a bit backwards I think. The idea of patterns is they describe commonly useful solutions, not designs you "should" use. Once you started to code in "pragmatic, minimalistic way" I assume you found you could apply the same solutions you had found earlier in new contexts. Those are your own design patterns. That is how design patterns work, some patterns of design "emerge", because they are the optimal solutions. A Design Pattern should be minimalistic, it should only do what is needed, not anything more. It should only solve its problem in optimal, minimal way. But if the problem it is solving is not your problem, you should not use it.
- Scarbutt 7y agoEven though there are books about design patterns, taking such a book and trying to "apply" its patterns is a bit backwards I think. The idea of patterns is they describe commonly useful solutions, not designs you "should" use. This nails it, and author does talk about it: Similarly, knowing about common architecture patterns is a good thing: it helps shorten discussions with people, who understand them the same way as you do. But architecture patterns are not the goal, and they won't substitute for simpler system designs. When designing a system, you might find yourself having accidentally applied a well-known pattern: and this is a good thing. Later, you can reference your approach easier. But the last thing you want to do is taking one or more architecture pattern, using it as a hammer, looking for nails to use it on.
- saiya-jin 7y agoI agree with you, but that's not how people usually progress. Its more in line with initial discovery of 'the best and battle-tested way to design code'. Immediately they try to apply patterns anytime they see an opportunity for it. They must be taken more seriously from now on, right? I get it, I went through exactly same hoops. My guess is, we all desperately want to be those aged and wise devs that nail the implementation in first go, without hesitation, knowing exactly what to expect and avoid, covering all corner cases with some elegant snippet. And learning about patterns feels like the surest way to get there. Of course as we know now that's far from truth, path to seniority can't be fast-tracked by reading a book or two and that's it. But inferiority complex is rife with junior devs, I mean you think you know a bit and then you encounter a codebase where you are completely lost. You see tiny piece of code that takes forever to decipher and fully understand. Who would feel great at that moment KISS was a thing 20 years ago, any probably even 40 years ago, there is no need to think now its different
- enriquto 7y ago> Next 4-8 years, I started getting cute with it and applied all kinds of design patterns, Factory, Abstractions, DI, Facade, Singleton you name it. What's cute about this madness? There's nothing uglier than that! Simple code is cute. The simpler, the cuter.
- hoseja 7y agoIt's an English idiom. See [1], point 3. [1] https://en.wiktionary.org/wiki/cute#Adjective https://en.wiktionary.org/wiki/cute#Adjective
- enriquto 7y agoThanks! I ignored this meaning (not a native speaker). Feeling a bit ridiculous now...
- abtinf 7y agoIn the spirit of being helpful: “ignored” implies you knew the meaning and intentionally did not address it. I think you meant “I was unaware of this meaning” or “I didn’t know that.”
- jbn 7y agonot to nitpick any more than necessary, but I don't think that's entirely correct: "I have ignored" implies that GP knew the meaning and intentionally did not address it, while "I ignored" is correct and also means “I was unaware of this meaning” or “I didn’t know that.” ... at least that's how I see it.
- enriquto 7y agoaccording to wiktionary [1], this is indeed a possible meaning of the verb "to ignore", but it is marked as "obsolete". This is a case of false friend when translating from french, where this meaning is the first one [2]. [1] https://en.wiktionary.org/wiki/ignore#Verb https://en.wiktionary.org/wiki/ignore#Verb [2] https://fr.wiktionary.org/wiki/ignorer#Verbe https://fr.wiktionary.org/wiki/ignorer#Verbe
- paxys 7y agoThis describes my career perfectly. And at every stage I inevitably get annoyed at other engineers not in the same stage as me.
- taneq 7y ago> I realize I code a lot slower now than in the past and that I write much fewer lines of code. It’s both good and bad because sometimes I’d even spend hours just trying to properly name a variable. I think the important thing happening here is more than just naming. You’re taking the time to fully consider what you’re doing with the new variable in order to name it. That’s time very rarely wasted.
- psychoslave 7y agoThere are other factors here: - how much time will the code be reread? - does the carefully selected name does make sense for other readers, future self included? That is, does it help to make things easier to understand for them? - a good name can be reused extensively over your whole career, so a few hours might well be well invested time from this perspective if you plan to keep a foot in the technical side of the story all along. Actually, a good name is surely something you might have the opportunity to reuse outside a coding context.
- croh 7y agoWell said. Similar to you, I code now almost exclusively in Flask. I don't want to spend days and night learning (and remembering for interviews) unnecessary abstractions and apis. Instead I prefer to spend more time on CS fundamentals, if I have to. Sad part of this story is broken hiring. Your resume doesn't get short-listed unless it comes with new hyped-shiny-toy. But this can encourage you to put more efforts on finding good employer. Apart from juggling act you mentioned, there is another caveat. Many devs don't understand exact use-cases of these design patterns and use them in wrong context. On foot note, If you don't have good team of engineers similar to OP, best way to craft your art is - - pick up a good library in your subject - start copying it line by line - when copying, try to understand everything - this will teach you lot about designing softwares Even though this sounds like stupid and time consuming, it is not. Believe me. You don't have to even reach 100 %, just try to reach 33 %. You will learn lot by this in short period.
- feketegy 7y agoMost devs prepare for the abstraction nirvana. I see a lot of fellow devs creating complicated code, because "in case we need to switch out the database down the road" or "what if we want to run the web app in CLI" In 20 years of programming I maybe seen one or two times a large application switched database engines and I've never seen a client want to run his/her web application in CLI... The art in programming is to decide whether you need that abstraction or not.
- Double_a_92 7y ago> in case we need to switch out the database down the road I would still kinda consider that. But the solution wouldn't be to create some massive abstraction. Instead I'd try to separate logic from database access code. I.e. avoid reading data from the database while you are in the middle of some calculation, or in the GUI somewhere. If you need the data do it beforehand and pass it in a generic data format.
- Vinnl 7y agoIt's not just programming, though. A large part of it is defining the product roadmap: code can get a lot better if a product team has the guts to unequivocally say that some features are not going to be part of the product, ever, such as running the web app in CLI. That's not just a programming decision - though I guess influencing the product roadmap could be seen as part of the art in programming as well.
- downtide 7y agoYeah I had a manager that was quite good at throwing out functionality. Didn't like it at the time, but they were mostly right - of course they were just trying to save money.
- james_s_tayler 7y agoI like this. Because time after time after time I have seen the opposite. Managers saying "but we might need it again later" and... We. Never. Have. And even if we did... its still under source control if you really want to dig it out.
- downtide 7y agoWhen I couldn't program I almost achieved more! I saved time by picking stuff up and glueing it together. Then later spent ages learning specific softwares, plugins and their wiring only for them to fall out of favour. Later frameworks etc. A web outfit I worked at should have concentrated on a few small plugins/components that would have handled most of their sites. Instead other behemoths emerged, that added pain and complexity to what should have been very simple sites. Only the author understood the ins and outs of a half finished product, that ended up bastardised for each project, resulting in multiple version hell. But hey this was before good 'Git'ing. Oh for hindsight.
- deleted 7y ago[deleted]
- amelius 7y agoIt looks like you were applying design patterns "just because". Obviously this is not a good thing. A better approach is to take some time and think about all the requirements of your project, and to take into account what requirements might be added later. With that in mind, you can choose the abstractions that you need, and from there start coding. That way, your design patterns start working for you instead of against you.
- naikrovek 7y agoyes, and the problem is that carefully engineering your solutions is not what's taught; applying design patterns is what's taught, both in college and at most/all large employers (certainly all of the ones I've worked for.)
- iask 7y agoThank you for sharing. Same experience here. I felt I was the only one going down this path. Many projects I look at has too many unnecessary layers files etc. I joined a company recently that has a simple app for end users to take orders over the phone, perform lookup and refunds. Something that can be built in a few days, seriously. When I looked at the code - WTF!!! The previous dev over architected this thing. Unnecessary layers, interfaces etc. one simple change can take hours. I think developers need a little bit of management experience to understand the impact of these complexity. At the very end, companies just want something usable to stay in the game...a Honda and not a Rolls.
- afpx 7y agoThe only problem is that it takes at least 10 years to get to that point. No one has found a shortcut, yet.
- nine_k 7y ago"Simplicity does not precede complexity, but follows it." This is why it's often worth it to come up with a complicated design, work on it without implementing it as much as possible, think it through, understand where the core functionality lies, and make a new, much simpler design.
- wonderwonder 7y agoI've always been happy with just OOP and dependency injection. Anything more and things start to get difficult to follow. Currently working on a a legacy system that uses micro services and it takes hours just to figure out where the code is that needs to be changed and to trace how those changes will be propagated through the system.
- yodsanklai 7y agoYou can't beat simplicity, but software aren't planned entities. They evolve from the collaboration of multiple persons with a variety of skills and personalities, working together to meet deadlines.
- GordonS 7y agoI've been on a similar journey, and I've seen this pattern repeat itself again and again! 1. Hack any old shit together, but it works 2. When you actually have to maintain what you previously wrote, you realise (1) doesn't work so well. Then design patterns seem like an epiphany, and you Cargo-cult the shit out of them, using them everywhere. You dogmatically eliminate all code duplicatation, use mocks with wild abandon, and are not happy unless you have 100% test coverage. For bonus points, you also overuse abstraction. A lot. 3. When you actually have to maintain anything you previously wrote, you realise what a tangled mess of abstraction you have made - you can't simply open a file and ascertain what it's doing! You also realise that the tests you wrote to achieve 100% coverage are crap, and don't really prove anything works. You finally reach a zen-like state, realising that simplicity is key. You shun all forms of dogma, and use patterns and abstraction, but only just enough