10 ms·
Tips for beginning systems and software engineers
- andy_ppp 10y agoI've been told conflicting things about don't repeat yourself; sometimes you end up making something wildly complex to avoid repetition - since learning functional programming I'm starting to think breaking things down into small reusable pieces that can be repeated in different ways is better.
- yisheng 10y agoI use DRY to enforce consistency. Bad repetition is when you or other engineers have to also remember to update the repeated parts.
- mikekchar 10y agoDRY has many caveats. For example there is the famous "Rule of 3" - On the third time you copy something, factor it out. But really DRY is simply a rule of thumb that is intended to get you to a better design. Because "good" with respect to design is a somewhat ephemeral concept, people often equate DRY with "good". This is incorrect as there are many DRY designs that are not good. In fact, my experience has been that for good design much more important than DRY code is simply reducing global variables (including classes -- which implies that you will be doing a lot of dependency injection). But, again, you can take it too far. I think my biggest piece of advice for beginners is to get a mentor and constantly ask for opinions about whether a design is good or not (and what about it is good/bad). It's not something you can learn from rules. It requires a considerable amount of experience.
- crdoconnor 10y agoIt's more fundamental than just a rule of thumb, but it's not the only rule of good code. Loose coupling (which implies getting rid of global state) is at least equally important, as is failing fast.
- mason55 10y agoOne problem is that junior and mid level developers have a lot of trouble figuring out good coupling because they are not experienced in the ways things can change. I was reviewing some code yesterday and the developer had a bunch of interfaces for everything so she could swap out implementations and she was very proud of how modular everything was. The problem was that all of her interfaces accepted a File object instead of a stream so we could change what the behavior is but not what it acts on which is really the opposite of what we needed.
- crdoconnor 10y agoIt's hard even if you're not a beginner. One of the hardest aspects of good design is choosing the appropriate level and location for coupling. I find that most developers who think that they can predict what kind of coupling they will need for future requirements tend to be wrong, too, no matter how experienced. Only backwards-facing refactoring really works at producing a good result, coupling-wise.
- ilyash 10y ago"breaking things down" is a very good approach. It helps clarity and often prevents code complication.
- lhnz 10y agoI would caution against following the DRY dogma too early or without thought about why people do so. I'd also caution against the "go DRY after 3" rule because it's not moored to the actual software architecture - it causes some code to be less DRY than it should be, and other code to be more DRY than it should be. DRY is generally meant to be an optimisation towards reducing the cost of change by reducing the amount of duplicate code (repetitive, busy-body work) but it often backfires and increases the cost of change. For example, say you are tasked with creating 15 widgets. During your first few sprints, every widget is the same, so your colleague berates you for creating them separately. You refactor them so that they all share a common implementation and continue. 3 weeks passes and features are being added left, right and centre. A point of contention within your team is whether to differentiate between 3 different types of widget with different features, or whether to use dependency injection. Development is slow because only one person can work on the widget code at a time, and the widget code has becoming increasingly complex as the number of callers invoking it have given it increasingly diverse requirements (note how looking at a function generally does not tell you which other functions call it, how they call it or why they call it). The question you should ask yourself in these situations is, how similar will these widgets be in 3 months time? If it's likely that they will diverge significantly then perhaps the best solution would be for them to be completely differentiated now: tested separately, easily updatable/extendable without regression bugs on other widgets, able to be worked on by multiple engineers without merge conflicts, etc. The unfortunate reality is that once you have made a unit of code DRY it is both time consuming and difficult to do this and politically untenable so people typically avoid it. When should you make your code DRY then? (1) You should make your interfaces DRY early on as this is a safe win without the problems of DRY implementations, (2) When you are very sure that the implementation of different units of code will not change in ways that do not create intellectual work of the following kind: "How do I avoid complecting these unrelated behaviours?" Having said all of this, perhaps this is not beginner friendly. Perhaps beginners should just follow DRY always. At the very least they will get practice refactoring.
- crdoconnor 10y ago>I would caution against following DRY too early DRY should only be avoided if the refactored code ends up being much more complex than the duplicated code and it doesn't reduce the duplication all that much. Pretty much every time I've seen DRY used to eliminate repetitions of three or more times it's been a good idea and 4 or more times it's been a no brainer. >The question you should ask yourself in these situations is, how similar will these widgets be in 3 months time? This is an awful approach. You won't be able to predict how similar they will be and you will likely create expensive to maintain architecture for projected scenarios which don't exist and neglect the architecture for scenarios that will. I've seen many codebases littered with architectural changes that were put in place for presumptive future features and then never used. It happens way, way more often than those architectural changes actually being used.
- lostcolony 10y agoThe problem with DRY is people will see "Oh, hey, I'm doing the same thing in multiple places. DRY says I should factor that out!" without actually thinking about whether it makes sense for there to be a dependency between those pieces of code. Moving duplicated code into a shared function/method/etc -creates an implicit dependency- between all callers of that function/method/etc. That's exactly what you want in some situations (and is in fact the benefit of DRY, i.e., because when you have to fix that code for one function, you have to fix it for the other(s)), but if you do it blindly you create a dependency between two unrelated bits of code, and that's where complexity comes from, as you start adding conditionals and things to try and make that shared code handle all cases. I find considering whether the repeated code is innately related, or in the same domain, or not, to be the best metric for determining whether I should refactor it to not repeat. It's adhering to the principle of least astonishment. Sometimes it should all stay separate. Sometimes a few pieces should be refactored to share code, and others left to repeat. Sometimes one set of the repeated code should share a function, and then the other set should share a different function, -even if that different function has the same implementation-, simply because the second set is so unrelated to the first. Sometimes they all should share the same code, because they're all related. Breaking things into small, reusable pieces helps avoid this because the dependencies you create are on small, easily understood, easily replaced snippets, which tends to have a very clear context/domain. It's easy to prove that the abstracted function is correct, since it's so simple, and it's clear what domain(s) it involves, since again, so simple, and thus the problem with any given bit of functionality is almost assured to be the composition of those functions, i.e., your specific use of it in this one instance. You can still run into issues if you reuse some of those composed functions, however. In general, the greater the complexity and domain specificity of the code you're trying not to repeat, the greater the danger you're shooting yourself in the foot. FP is especially beneficial, as such, because the higher order functions that tend to be common are both pretty simple in their function, and pretty universal in their domain.
- thex10 10y agoThis comment helped me immensely. Thank you.
- deleted 10y ago[deleted]
- js8 10y agoI would add two things to this: 1. Automated tests are not the only way to write code with less bugs. The other two, currently less in fashion but IMHO very important, techniques are abstraction and assertion. 2. In my view, there are two kinds of automation - bad and good. Bad automation checks if humans did some process correctly. Good automation implements some process without human intervention. It's very easy to start working on bad automation rather than the good.
- dfischer 10y agoHave any examples to clarify point two?
- js8 10y agoWell, an example I have from work, where we have a certain very old and obscure process to build fixes for our application. It's somewhat a manual process. Somebody wrote a program to check if this manual process was done correctly. It prevents problems, but better would be to replace this manual process with an automated one, however that would mean to change the process somewhat to accommodate the automation (this is sometimes necessary since humans can deal with weird exceptions much better than machines). Another example could be used linter or valgrind to prevent C-style bugs with pointers and allocation, instead of using a high-level enough language, such as Java, to avoid those in the first place.
- dfischer 10y agoYeah it's an interesting perspective. Thanks. I'm looking at it from a product UX perspective. It's a good challenge. Thanks for the insight.
- crdoconnor 10y ago>Automated tests are not the only way to write code with less bugs. It's absolutely a prerequisite. Humans can model the behavior of small parts of a code base without the aid of automation, but once a code base grows beyond a certain size, you either need to have automation to have a handle on the code or let it congeal (which ends up accelerating the creation of technical debt). There are bad tests just like there is bad code. All of the rules for good code apply to tests too.
- gremy0 10y agoI don't really get the Ease vs Simple section, what point is he trying to make? This is simple: i = 0x5f3759df - ( i >> 1 ); Where this is just easy: famework.authService.login('user','password') Good article though.
- ilyash 10y agoThe point is that in the second case some people have no clue what's really happening, others have rough idea and very small group of people know what exactly happens. It might appear simple but it's not. It's easy.
- deleted 10y ago[deleted]
- scriptdevil 10y agoIf you haven't already done so, listen to this talk by Rich Hickey (the creator of Clojure). This should clear it up for you. https://www.infoq.com/presentations/Simple-Made-Easy https://www.infoq.com/presentations/Simple-Made-Easy
- ilyash 10y agoThanks! Added link from the post to the lecture.
- Sammi 10y agoEasy things can be vastly complicated. This means that such complicated easy things are hard to maintain. Simple is always easy to maintain. Remember, as software developers, complexity is the enemy. It is the beast we want to slay.
- wtbob 10y agoThis is simple: if (str.length < 6) { str = ' '.repeat(6-str.length) + str; } This is easy: leftPad = require('left-pad') leftPad(str, 6) (note: my JavaScript is rusty; there may be some very valid reason that the simple code is wrong)
- aavotins 10y agoI can add another thing from my personal experience - if you've just started out and you land in a job where you're the sole warrior or you don't see a single person who's smarter than you, get out as fast as you can. In best case scenario you will not learn anything, in the worst case scenario, you will learn bad things that you'll eventually have to unlearn.
- deleted 10y ago[deleted]
- ilyash 10y agoAgree. Can you please add it in the comments on the blog or alternatively, can I quote you there?
- aavotins 10y agoFeel free to quote me and adjust my quote to be better sounding.
- mobiuscog 10y agoJust be cautious with: "if you've just started out and ... you don't see a single person who's smarter than you" Many people starting out think they're much 'smarter' than they really are - experience often counts for much more. Of course, you could also have landed in a real dead-end company.
- gregdoesit 10y agoI am glad you put automated tests as one of the first ones. To this date I find that many people - often mid level, and sometimes senior developers - are very uncomfortable with autmated tests. As a result they don't use this tool, and end up being less productive, and often write more complex than needed code - and of course code with more bugs that could have been avoided. I'm no TDD or automation advocate - however I do see that this tool is essential to modern software development, and the only way to learn to use it, is by practice. I would encourage every engineer who's covered the basics to go full-in on TDD with 100% test coverage for a few days or weeks. Not because it will make you more productive or less bugs (you will be less productive), but it will change your thinking of writing code (similar to how learning Lisp will have a massive impact on your thinking of structuring code). TDD + 100% coverage is an extreme, which is worth experiencing to take learnings from it. Later down the road, when you've gotten into the habit of developing with no, or barely any automation, it will be much more difficult to learn to fit this tool into your coding routine.
- cm3 10y agoTDD can be tedious at times, but if you do it, you quickly reap the benefits. It makes testing easier, it reassures you that you didn't break everything completely with a random change, and it keeps your API in check. And it makes more of the process reproducable.
- mbrock 10y agoTDD is a kind of semi-formal specific process with a whole legacy of consultant workshops and all of that stuff. So I think it's great to mostly ignore that, since it's contentious, and focus on that core question: how do I know that the system works correctly? This also makes it obvious why type systems are a glorious ally, in addition to test suites and proofs. And it's a core question because if I don't have grounded confidence in the system's correctness, then that's likely to be anxiety-inducing, irresponsible, and sometimes dangerous.
- cm3 10y agoRight, and I didn't learn TDD formally. Sorry for using it freely. I write tests in tandem with the implementation and do not write tests and only then the implementation.
- ilyash 10y agoThere are many good points in the comments here. Thank you! I have added a link from the blog post to this discussion.
- Kurtz79 10y ago"Learn at least 3 very different programming languages. I’d suggest C, Lisp and Python. The chances are that you will not find a Lisp job but it surely will make you a better programmer." (I understand these are suggestions and we are in the realm of personal opinions). Even as a Systems/Embedded programmer it's hard for me not to consider HTML/Javascript a must-have, this day and age (not a nice-to-have). Even in embedded/industrial systems nowadays it's perfectly normal to have a maintenance interface based on a lightweight web server.
- ilyash 10y agoChances are that you will need this but it's hard for me to recommend JavaScript as a starting point so it's actually a "must-have but a bit later" in my opinion.
- Sammi 10y agoYeah. Don't worry about whether or not you should learn javascript. It will be forced upon you sooner or later.
- patsplat 10y agoAgreed. C, Lisp, and Python are all integration languages. There's a fair bit of PL history in there, but it's mostly one problem set. A better mix would be: Python, Javascript, SQL, and Regular Expressions This mix would cover integration, ux, persistence, and madness.
- vvanders 10y agoI still think C/C++ needs to be in the mix there somewhere(or even better, assembly). Even if you don't write them day-to-day knowing how pointers and memory works can be invaluable.
- joshuaduffy 10y agoI'd suggest a statically typed language too. Java/C# etc
- any1 10y agoIt's usually managers who buy the hype. They're the ones who need to be told to be sceptical. Nice post, by the way.
- thecopy 10y agoDo i really _must_ know 1) Big O notation, 2) common algorithms and their time and space complexity, 3) which opcodes exist for a CPU of your choice and 4) a kernels main system calls? I dont know any of these. I know they exist, i know of the concepts, but no specifics. I would argue that these are good to know, and a must if you are working in a domain where this matters. For design, architecture and implementation in a high level language when the problem do not touch these areas this is not a MUST have.
- ithinkso 10y ago1) and 2) are a MUST across the stack I believe. A lot of those crazy snippets from thedailywtf etc. come from the lack of this knowledge and suddenly performace suffers a lot when someone tries some O(n!) for simple stuff. Also, not to put anyone down but... it is kinda embarassing imho to not know how fast can you sort.
- lostcolony 10y ago3 and 4 are only needed in specific domains. 1 and 2 you won't need every day. You can probably even make a career out of software not knowing them. But every software related domain will have times where knowing them will help you, and where not knowing them risks an inefficient and slow implementation and no way for you, individually, to fix it in any sort of reasonable time; you'll either have to learn it then, or you'll be reliant on someone who does know it fixing it for you. I've had to address performance issues where knowledge of common data structures and algorithms' time and space complexity (and/or being able to look at an algorithm and realize "...this is O(n^2). It only needs to be O(n). WTF", and fixing it) was the key in Javascript, Erlang, Python, and Java. Exactly what 'high level language' do you feel you won't eventually need this stuff in?
- Sammi 10y agoCode written by people who don't know 1 and 2 tends to make the mistakes that you learn to avoid by learning 1 and 2. And these are mistakes that you can make anywhere in your code and that can be very costly in performance and correctness.
- objectified 10y ago
- objectified 10y agoI must say that the more I read articles of this character, the more I feel that it would make sense to distinct between different types of "engineers". A lot of the proposed advices are applicable only in certain fields, and not so much (or not at all) in different fields, whereas every field can go very deep (so it's a waste of time gaining knowledge you'll never use there). While a number of these advices are fairly generic, others definitely are not. A front end (client side) web developer won't benefit from having deep knowledge of data structures or the kernel, but he will benefit from knowing about Big O notation. Additionally, he will benefit lots from knowing how browsers work, and having deep knowledge of HTTP. Someone working on ETL processes all day will not benefit much from knowing about interpreters and compilers, but will benefit from knowing a lot about UTF-8. But also, he will benefit lots from knowing a large number of layer 7 transport protocols. A systems programmer (defined as "someone who programs close to the hardware") won't typically benefit much from knowing javascript or how browsers work, but will benefit lots from Big O notation, data structures and algorithms, and C. And so on. I'm seeing a future in which we will be able to more distinctly treat separate specialisms within the field that we now call "software engineering" (or countless variations on it).
- ilyash 10y agoAs with any other advice, one should use common sense to see how and which parts of it apply. > A front end (client side) web developer won't benefit from having deep knowledge of data structures but basic knowledge is still a must I consider the basic knowledge of the "must" sections really a must for everyone. The depth might vary.
- BugsBunnySan 10y agoNice post! I hope lot's of people read and understand it, the world will be a better place for it :) One thing that would be good to add is: If you implement a way to create/add something, implementing a way to delete/remove that something isn't optional. As Lovecraft put it: 'Do not call up that which you cannot put down.' This will result in a system that keeps proper track of stuff.
- nekopa 10y agoCan we somehow change UTF-8 to WTF-8? Over the last 12 years or so I have constantly been bitten in the ass by UTF-8. It first started when I moved from the US to Europe and decided to help people make websites in their native language. More recently I was developing a piece of python code to help me with a thing. Running into UTF-8 problems made me decide to do a deep dive into what it is and how it works. After a week of diving down a rabbit hole I am still none the wiser. Blog post after blog post, video upon video, and it still doesn't click for me. Yes, I solved my problems, but in a black box type of way. Can anyone recommend a good book or long source to help me with this?
- peterwwillis 10y ago> Automated healing / rollback Jesus christ on a cracker, please use this sparingly. It's one thing to have buggy software. It's another to change versions of software right underneath your users. This is like polishing a dance floor while the DJ is still spinning. It doesn't matter if you're reverting to an old good version; unannounced, unplanned & unexpected changes lead to disaster. Known bugs are better than unknown changes. > Don’t duplicate code Yes, this is a really bad idea, but forking is the cheapest possible way to make changes that break as few things as possible. Yes, it's a nightmare in the long run. Ask your boss if he really cares about the long run. Everything else was spot on.
- eternalban 10y agoMissing advice: Learn to read specs.
- karlgrz 10y agoGood list. I would add "Software that does not ship is essentially useless." To clarify, I mean that you can polish and try to perfect as much as you want, but at some point you have to get it into the hands of users to be actually useful.
- cmcginty 10y agoI'm not convinced on servers must be set to UTC timezone. Maybe just write your server code to read/store UTC time, which is easily calculated from any timezone.
- douche 10y agoTime should be converted to UTC at the earliest opportunity, and kept that way as long as possible. Also, always, always, always, serialized in ISO-8601 format if it is getting turned into text.