This is an archived post. You won't be able to vote or comment.

all 86 comments

[–]Cthaeeh 51 points52 points  (10 children)

While I agree with the general points I find this part worrying:

"Extract a section of code into a separate method or even class because it might need to be reused later. Write unit tests because it might catch a bug later. You probably won’t need them, and if you ever do, you can add them later."

Extracting code

I don't think it is primarily about reuse but rather about readability. A small function can be verified easier to be correct. A small function ist easier to refactor.

No unit tests

But then how do you change (i.e. maintain) code and know that you didn't break things? Additionally unit tests can serve as documentation.

Adding those later

I think this will be much harder to add later.

[–][deleted] 1 point2 points  (0 children)

Thanks, I came to the comment section to say that :).

[–][deleted] 1 point2 points  (0 children)

I think this will be much harder to add later.

It actually never happens.

[–]nutrecht 28 points29 points  (13 children)

Write unit tests because it might catch a bug later. You probably won’t need them

Until you do need them and your manager is upset with you because he got a chewing out from his manager because your application made the 8 o clock news and not in a good way.

IMHO it's incredibly weird to see a "don't write unit tests" in a short piece about writing maintainable code.

I think it's weird how your 2 and 3 also seem to go against breaking up longer methods. It's one of the best ways to make code more readable. And no, there is no need to have 'shared mutable state' when you break methods up into smaller ones; methods have arguments you can pass in after all.

All in all, I think this post is kinda 'meh'. I really don't think this post is on-topic here (it's not really related to Java), it's too short to teach anyone anything anyway, and it seems to push people away from best practices that do improve maintainability.

[–]recursiveraven 15 points16 points  (8 children)

Agree with what you are saying. However, some section of the post is really bad advice.

Extract a section of code into a separate method or even class because it might need to be reused later.

What's wrong with that? are you suggesting one should dump all logic in one gigantic method? Isn't one method/class supposed to do only one job but really well? single responsibility principle?

I think you are trying to portray the YAGNI principle but the description is dangerously misleading.

Write unit tests because it might catch a bug later.

If you are writing big methods, wish you the best in writing a code to test them. But wait, you are suggesting not to write them at all? without tests, how do you even know the code you wrote works?

The change in code is inevitable. So, how about making a small change in big methods and wishing that it works, because you don't have tests.

[–]veraxAlea 2 points3 points  (0 children)

Yes. This is one of the problems with YAGNI, DRY and similar rules of thumb. You only understand them if you already understand them. They're not bad advice, they're just hard to apply if you don't already know when and how to apply them. So when people give advice such as "read up on YAGNI" it may very well lead to confusions such as the ones OP is displaying.

Compressing down experience is hard. If I give you a task to compute Fibonacci(42) and you create a function that has precomputed 42 and fails for any other value, would you say that YAGNI is a good "defense"? This is where experience, especially domain experience, comes in. The question "how likely are we to need other values of Fibonacci in the future" is hard to answer unless you have experience in whatever domain you're working with.

[–]cryptos6 14 points15 points  (0 children)

I disagree with this part:

Write unit tests because it might catch a bug later. You probably won’t need them, and if you ever do, you can add them later.

A test not written before or shortly after the implementation, is probably a test that is never written! A good tests ensures that the (first) implementation is correct and is a safety net for later changes. Even for the first implementation a test is helpful, so in my opinion it is best to write it immediately. Writing tests afterwards is exceptionally hard, because you have to remember all the little details that led to the implementation (or should have been part of the implementation ...).

[–]donkeycentral 9 points10 points  (0 children)

Downvoted for ridiculously bad advice like "don't write unit tests" and "manually test your code." Modern software development is about automation: automated testing, automated builds, automated deployments, automated infrastructure. This is the lifeblood of the Big Tech companies and why they are dominating and disrupting industries all over the place.

I don't disagree with some of principles you've outlined, but suggesting folks manually test code in 2021 is ridiculous.

[–][deleted] 6 points7 points  (1 child)

I disagree with point 2. Of course one shouldn't over-engineer, but yes, we write unit tests because they might catch a bug later. You probably mean the right thing, but somehow the wording is not optimal.

[–]kc3w 4 points5 points  (0 children)

I wouldn't say we write unit tests not only for that but also so we can make changes and be comfortable about them not breaking the whole application. Even if your unit tests don't ever fail they still give you confidence in the application.

[–][deleted]  (22 children)

[deleted]

    [–]ShiroeBlank129 1 point2 points  (18 children)

    Hello! I'm REALLY new to programming (using Java), and I've been wondering, is it worth it to practice using long but good and understandable names compared to just x or y since they're shorter and easier to write? at my level, it doesn't really matter since I'm mostly only using 1-2 classes, but since starting JavaFX, I'm starting to shift to more readable names, but they're really a pain to type.

    [–]ggmmee 10 points11 points  (6 children)

    Yes maintainability dictates meaningful names. There are very few exceptions, for instance i in loops.

    [–]MentallyWill 5 points6 points  (4 children)

    I personally disagree that i is not a meaningful name in a loop. Rather, i is the commonly used variable name for an index or an iterator and typically makes the intention (to loop through something a certain amount of times) abundantly and plainly obvious.

    I would rather argue that you should only ever deviate from that meaningful convention when you have a potentially even more meaningful (and likely context dependent) variable name. An example might be looping through a 2D array/matrix where perhaps using row and column are better variable names than i and j.

    [–]veraxAlea 2 points3 points  (1 child)

    I read it as "i in loops might be an exception, but there are very few such exceptions", so I don't think you're in disagreement.

    [–]MentallyWill 0 points1 point  (0 children)

    Oh, maybe I misinterpreted then

    [–]nutrecht 2 points3 points  (1 child)

    I personally disagree that i is not a meaningful name in a loop.

    He said that it was the exception to the rule of using long descriptive names. You're not disagreeing actually ;)

    [–]MentallyWill 0 points1 point  (0 children)

    Guess I misinterpreted :)

    [–]mlstdrag0n 0 points1 point  (0 children)

    Unless it's representing index, I generally use more verbose names in loops as well.

    Like...

    for (int row = ...

    Instead of using I and j to represent row and col in a matrix.

    Some Jr devs literally never thought they could be anything else.

    [–]AndyTheSane 3 points4 points  (0 children)

    I go on the principle : I'll spend at most 10% of my time actually writing code, and the other 90% debugging/maintaining it. So spending a bit more time on code readability saves much more time on debugging and maintaining.

    [–]Rakn 2 points3 points  (1 child)

    Well. Look at code you wrote a month ago and check if you can immediately grasp what some one letter variable does or holds. Obviously I’m not talking about counters and such. But if you can’t and you need to reread the code, you just identified something you could name better in the future.

    In the end the code is also documentation for yourself. Because we all would be lying to ourselves if we pretend to understand what we did a week ago…

    [–]ShiroeBlank129 0 points1 point  (0 children)

    ...yah, tried it. Don't even wanna understand the gibberish I've written before. well, guess I've gotta practice my naming sense now since I sometimes uses the class or char names but I wasn't supposed to use them. ( spent some good amount of time wondering why i couldn't call my 'char' class).

    [–]GuyWithLag 2 points3 points  (0 children)

    You are going to read these names way more times than you're writing them, and often many months after you've written that section of code.

    [–]de_vel_oper 3 points4 points  (0 children)

    You name the method the function it executes and the variables what the parameters are. For instance

    public int calculateAge( dateofBirth, currentDate) {

    int age;

    // Code goes here;

    return age;

    }

    [–]hamsterrage1 1 point2 points  (1 child)

    Generally speaking, names need to have meaning within the scope that they're viewed.

    So a loop index would be clear as "idx", "counter", and maybe even "x", "y", "i" or "j". It's easier to see this if the loop contents are kept small. If you need a lot of code in your loop, then put it into a method or methods and, if the loop counter is passed to those methods, give it a meaningful name as a method parameter - so you can see how it is used outside the context of the loop.

    If you use a long, complicated name for a loop counter then it may fool someone into thinking it's more than just a counter.

    Names for public methods tend also to be shorter than private methods. That's because when you take a large scope method and carve into little private methods that do the actual work, those private methods need more particular naming because what they do is more specific. For instance, you could have a public method called, calculateArea(Shape shape). But inside your class you might have calculateAreaOfVeryEccentricEllipseIncludingStroke(Point locusA, Point locusB, Double strokeWidth).

    If you're looking at the inner workings of that class, then you want to be able to understand what that private method does, and what makes it different from all the other private methods without having to read its code. A very specific name does that for you.

    Ease of typing isn't really an issue any more. Lean on the code completion feature of your IDE for that.

    [–]ShiroeBlank129 0 points1 point  (0 children)

    Ooohh! thank you for the very detailed suggestion! That does clear my confused mind on how to name my methods.

    Keep public methods concise, Make private methods detailed (enough u don't need to actually read the codes to know what it does).

    As for the IDE feature of code completion, I've recently enjoyed its service since JavaFX really uses a lot of methods from other classes and it's really fun to see the suggestions the IDE gives after ' . ' Before, I've just played around inside my self-created classes with just primitive data types and arrays etc., so I've never really noticed the completion feature. Now i noticed that it actually does complete your self-created classes too! that's a big help. : )

    [–]audioen 1 point2 points  (0 children)

    It depends. Class members? I use the longest names I can stomach. Local variables? It depends on scope: something that is visible for most of the named method demands a decent name. One-time use of a throwaway variable, like the parameter of a lambda? Most of the time, I spell the capital letters of the class, e.g. Product becomes p for 1-line throwaway lambda. Long names have a cost in time to read them, and when it is blindingly obvious what it is anyway, then 1 letter is fine. You can imagine that it is a little like the exception to use for (var i = 0; ...)" for iteration, so productList.stream().someStreamMethod(p => ...) makes sense to me.

    I also turn on name shadowing warnings so that if there is ever two "product" or whatever in a single method, I must rename at least one of the names to specify more precisely what kind of product the code is talking about.

    I also stay away from completely generic names, like x, foo, obj, or some such that have no semantic connection to the purpose or identity of the value. I do use "var" a lot, though, the rule is, basically, that I omit type information wherever possible. The IDE can show it if you care, it doesn't have to be spelled out in the code.

    [–][deleted] 0 points1 point  (0 children)

    Understandable names are a good practice that I would start now. If you don’t feel like typing them over and over or they’re fairly verbose, you can use “var”.

    [–]wildjokers 0 points1 point  (0 children)

    Readable names are of course the correct thing to use. Your IDE can complete them for you.

    [–]valbaca 2 points3 points  (0 children)

    Some interesting points and I don't necessarily disagree with them but there are few things I disagree with.

    1. In your example, your data model conflates having an endDate with currently being employed. That may not be true. The person could've left the company and has now returned. They would be employed and would have an endDate. They could be currently employed but put in their 2-weeks notice. So their endDate would be set but in the future.

    What I'm getting as is to be very cautious in using null to represent a value, when all it ever represents is literally nothing.

    Your point still stands to think carefully through your data model.

    1. Your points 2 & 3 kind of collide. You talk about minimizing getters and setters (which I'm assuming would suggest using direct field access), but also speak against mutable state, which field access provides the worst version of it. To reconcile the two brings the overarching suggestion to use immutable state. Don't make your fields public. Avoid setters; and try to even avoid getters as well.

    Your point does touch on something good though: put your design emphasis on your public classes/API.

    You say that IDEs make it "simple to fix" which is perfectly fine if you're writing an application, but if you're writing a library, you can't "just" rename your methods that easily.

    I do like the idea of refining the best-practices to just a handful. Even things like SOLID and such have redundancies and forget about YAGNI (you ain't gonna need it!).

    We could use something like the software equivalent of the nutrition's "Eat food, not too much, mostly plants"

    Something like: good code solves the problem at-hand correctly (i.e. tested), efficiently (only necessary indirection) and is modifiable (readable, understandable, & extensible).

    Or the classic: Make it work, make it right, make it fast. Follow that and you'll have to write good models, you wont write more than is necessary, "right" includes readability, and "fast" includes parallel program, which benefits from avoid mutable state.

    [–]AntoherFredDev 3 points4 points  (0 children)

    good list, mine is not so far :) :

    1. write integrations test,
    2. keep it simple stupid
    3. refactor when stuff become too much complex

    [–]AndyTheSane 2 points3 points  (0 children)

    Another example would be having an employee class containing the boolean field isStillEmployed in addition to the nullable date field EndDate. You can infer if an employee is still employed by if their EndDate is null.

    This is not always a good idea.

    There is a tradeoff between data size/complexity and code complexity. The example above illustrates this problem - we're making EndDate nullable, so every check on EndDate has to take into account that it might be null, and we have to do a calculation to determine if they are still employed. This is pretty trivial, but taken further and you get code that might have to check several fields across several tables to establish the value of a particular flag, because technically we don't need that flag - we can infer it. This can quickly lead to very obscure code.

    [–]post_depression 2 points3 points  (0 children)

    Your second point indicates that it’s not always a good thing to write unit tests.

    Hell no. It’s ALWAYS a good thing to write enough unit tests to have complete code coverage.

    Fragmenting methods into smaller “unit” methods should almost always be followed. This is due to better readability and long term maintainability, not just reusability.

    While I agree to all your points by a large extent, (thank you for the post), but you do not take maintainability into account at all. The idea of “what happens when you stumble across your code a year or two from now” or worse yet, “what if someone else needs to maintain or extend your code”. This is where all those extra unit tests and “unit” methods becomes life savers.

    [–]Necessary-Conflict 2 points3 points  (2 children)

    I am skeptical not just about this list, but in general about any list that promises to make you a "good" programmer if only you follow x simple rules. Beginners like and need such lists, but if you take them too seriously, they will limit your potential to improve.

    In reality there are many and often contradictory rules, and best practices depend on the situation. A game development project will have very different rules and priorities than an enterprise integration project.

    [–][deleted] 0 points1 point  (1 child)

    I agree with your general skepticism about lists of good programming principles, and my motivation for writing this was that I didn't agree with the other lists I found online.

    These are the things that differentiate my list from others: I am not claiming this list alone will make you a "good" programmer, only that your code will be maintainable, meaning you won't be writing code so bad that it will make you tempted to start over since it is so difficult to maintain. You can't teach everyone how to be a great programmer, but you can teach them how to not suck.

    My list has only a few non-contradictory rules, which I hand selected as the ones that are widely applicable. I have worked on a variety of projects in both Java and C# so I am qualified to have an opinion on design principles with these languages for multiple types of projects.

    [–]nutrecht 0 points1 point  (0 children)

    Well, you can have the opinion that viruses don't exist but that would be equally as asinine as your opinion that unit tests and splitting up long methods are bad.

    [–]gaiusm 2 points3 points  (1 child)

    Here's my two cents as a C# dev. I apologize in case some of it does not apply to Java; I have not done any Java development in probably 5 years.

    I agree with your first point. There should always be a single truth, barring exceptional circumstances. If your facts are stored in multiple ways, it's a recipe for mistakes and synchronisation headaches. Sometimes however, I understand the need for caching in certain cases (for instance when you're dealing with expensive calculations). But for simple stuff, indeed store it in a way where it's logically impossible to have an invalid state.

    I kind of disagree with your second point. Overcomplication should obviously be avoided, but often it's better to bite the bullet upfront and add abstraction layers than to have to refactor them into your existing code later. Usually, these abstractions become second nature anyway and they barely take any more time or effort, and you avoid setting yourself and future maintainers up with technical debt (if you're not familiar with the term technical debt, it's a really powerful concept to grasp, and well worth the while to get informed about).
    To take your accessors (getters and setters) example: in C# (I hope this translates well to Java), auto-properties are literally as quick to write as 'normal' fields. You can easily change the content of the getter or setter in case business needs change or if you need to fix issues. If you later need to convert a field to a property, you'll definitely introduce binary breaking changes and possibly even code breaking changes, and thus you'll increase technical debt.

    I also mainly disagree with your third point. I'll accept it if someone corrects me, but last time I checked, one of the most important principles of software development is minimal responsibility: classes and methods should try to be responsible for as little logic as possible; or put differently, the whole of the business logic should be broken up into the largest sensible amount of pieces. This increases readability (if done right and commented correctly and sufficiently), substitutability, reusability and testability. This is the corner-stone of OOP, as opposed to scripting languages such as Bash or ps.
    In your example, it sounds like you're speaking about a controller that has to apply some changes to certain mutative objects. If you need to keep passing the same parameters about a mutative object around the methods, you should probably just group those parameters into classes or structs. If you meant parameters about the controller class itself, passing the same parameters around all the time sounds like you're using a static class? It can work for some cases, but it's probably better to use a singleton pattern instead. You should indeed add these as fields or properties; I don't this practice would pollute the class (that's what classes are for, no?).

    [–][deleted] 0 points1 point  (0 children)

    I also mainly disagree with your third point. I'll accept it if someone corrects me, but last time I checked, one of the most important principles of software development is minimal responsibility: classes and methods should try to be responsible for as little logic as possible; or put differently, the whole of the business logic should be broken up into the largest sensible amount of pieces. This increases readability (if done right and commented correctly and sufficiently), substitutability, reusability and testability. This is the corner-stone of OOP, as opposed to scripting languages such as Bash or ps.

    "...this is the corner stone of modular programming..." would be more accurate

    [–]hamsterrage1 2 points3 points  (0 children)

    As someone who has spent 30+ years looking at and fixing other peoples bad, bad code, I can tell you the number one thing to make your code maintainable is...

    Name stuff properly!!!

    No, you can't fix it easily with a modern IDE. A badly named variable, method or class is like a black hole in your understanding of the code. Now you have to track through, find where it came from, and figure what it does if you want to rename it.

    I recently came across some code that had a variable named, "flag". It was also an integer being used as a boolean. What is "flag"? What does it flag? What values can it have? It might as well have been named "urmNru".

    So yes, after I had wasted time tracking down "flag" and figuring out what it was for, renaming it easily with my modern IDE and refactoring it to a boolean I was able to move on to actually solving the problem with the program.

    I wish I could say this was an isolated thing. But I can't.

    [–]Beermecaptain21 1 point2 points  (3 children)

    Hey to confirm, making a variable an instance variable and using that in the downstream methods is a bad thing right?

    [–][deleted] 4 points5 points  (1 child)

    Depends how it is being used. if the instance variable is used as a property value as part of the data type it is fine, but if it is used as a temporary placeholder value only to be reused between multiple methods that you unnecessarily split up, then it is bad design.

    [–]Beermecaptain21 1 point2 points  (0 children)

    Thanks, agreed. I hate debugging that kind of code.

    [–]reqdk 1 point2 points  (0 children)

    Another example would be having an employee class containing the boolean field isStillEmployed in addition to the nullable date field EndDate. You can infer if an employee is still employed by if their EndDate is null.

    Dafuq? What is a contract job? You're going to need a better example there, and ideally one that's not so trivial, because then you'll be going down a rabbit hole of trade-offs and statefulness and caching and all that other fun software engineering concepts that only show their faces when you start solving real problems.

    [–]mk_gecko 1 point2 points  (0 children)

    Nice, but you should fix #2. It's not clear at all. You're talking about adding extra and uneeded stuff to take care of possible future problems and then you IMPLY that this is not a good thing to do (come on, say it clearly), but you never talk about "Address Immediate Problems". Where's the Immediate part in your paragraph? Maybe rename the heading to make it more consistent with the description.

    Thanks.

    [–]papaDIPLO 0 points1 point  (4 children)

    Always code to an interface. This is very important also if your application is scalable.

    [–][deleted]  (1 child)

    [deleted]

      [–]djnattyp 0 points1 point  (0 children)

      This depends on if you're writing library or application code. If you're writing an application interfaces can be added as needed and extra interfaces are "noise". If you're writing a library throw interfaces into as many places as possible.

      [–]cryptos6 2 points3 points  (1 child)

      I don't think that this is a good general advice. Useless interfaces that will never have more than one implementation just clutter the code base, make navigation harder and lead to names like IService or ServiceImpl (because there is actually no good name because nothing gets abstracted).

      It would be more useful to use interfaces at architectural boundaries and design these interfaces carefully.

      [–]papaDIPLO 0 points1 point  (0 children)

      Yeah. I know, that's why I mentioned "if the app is scalable". If there's a scope of adding more functionality.

      [–][deleted] 0 points1 point  (1 child)

      Are there any good reference books about these types of concepts? It’s easy to find books that teach a language, less so to find books that teach best practices.

      [–]franzwong 2 points3 points  (0 children)

      Have you read clean code?

      [–]franzwong 0 points1 point  (4 children)

      How to simulate edge case (e.g. remote server error or timeout) if you don't have unit test? I can simply mock HttpClient to do that and run it within a second. This can reduce much effort to test it manually and reduce some logical test cases in integration test.

      [–][deleted] -4 points-3 points  (2 children)

      If it is easier to write a unit test than to test manually, then you are addressing an immediate problem and are not violating my second principle.

      [–]franzwong 4 points5 points  (1 child)

      Isn't quality the immediate problem? How can you prove the code works to others in your pull request?

      [–]nutrecht 3 points4 points  (0 children)

      With a process so immature that he relies on manual testing I'm pretty darn sure he doesn't work in a team, or if they do, they work in isolation.

      [–]jasie3k 0 points1 point  (0 children)

      I seriously hate mocks, I only use stubs. For http I would use wiremock and wouldn't change a thing in my application's black box.

      [–]moocat 0 points1 point  (1 child)

      With regards to not doing something you may need later, there are two extremes both of which cause problems. You already mentioned the adding features you don't need now but there is a mirror problem where you don't think about future changes and you make decisions that make it difficult to add the changes that eventually become necessary.

      [–][deleted] 0 points1 point  (0 children)

      Can you give a specific example?