Things I learnt the hard way in thirty years of software development
blog.juliobiason.net
blog.juliobiason.net
Your throwaway prototype will be the codebase for the project.
In my career, I've seen multiple throwaway prototypes that were hacked together. None of them ended up in the waste bin.
"Just add X and Y and you're done". "But this is just a quick hack to show the interface, there is nothing behind it!" "Yes, and the interface is fine, so why start from scratch? Just add the rest".
Now I know: I never build throwaway prototypes ever again, it's always a base for the project to come.
No one ever wants to hear that part.
Perhaps this will help your case: https://martinfowler.com/articles/is-quality-worth-cost.html
Then I sat down and rewrote it in about a week, and it was much better the second time, and turned out to be the start of a code base that's still in daily use and evolving 25 years later.
The lesson I took from that is, don't write throwaway code, but given a chance, throw it away anyway.
But, until you've done this for 20 years, it's sort of hard to imagine going back to code you wrote 20 years earlier to work on it again. Many of those "best practices" take on a much more concrete meaning. In many cases, I've been ... moderately proud of how well some decisions stood up, and in other cases I remember exactly what I was doing when I cut a particular corner (was late getting home that night, needed to get back home before a winter storm, etc).
Until then, if it's solving a business need and you're not pouring tons of effort/money into it, it's more about pride than a real problem. That is a lesson I had to learn the hard way, and still struggle with: accepting that sometimes a janky, hobbled-together solution that solves a business problem still solves that problem and might not be worth the cost of "fixing."
1) Make sure that I didn't have to handle some messy deployment when it inevitably would become rebranded to production code.
2) Make sure nobody in their right mind would try to extend the functionality and keep me responsible for fixing the inevitable wreck.
Obviously it was put into production, still is, and has not been extended with anything except the initial functionality.
I don't know if that counts as a success or a failure, but at least deployment was never an issue!
The prototype is a showcase that allows to show how development could be faster if there was less stupid requirements and less coding constraints.
"It's enough to do an experiment," he said. "The problem is the experiment never ended."
https://www.networkworld.com/article/2227543/software-why-ip...
6 months later, an angry customer wrote to my boss about the "Energy Saving Monitoring System" somebody sold to them stating that some kind of SSO wasn't working as expected.
My boss handed the case to me and I, flabbergasted as I was, proceeded to delve deeper into the strange report about that system we were sure we've never written, let alone sold to somebody.
Long story short, it was my quick demo of a content heavy portal, turned into that monster of a "Energy Saving Monitoring System" - whatever that is - by the partner we demoed it to, and sold to several customers as a finished product.
Applies to almost every bit of code I've written.
I think that cuts both ways - often the quick fix is good enough, while the "proper fix" is over-engineered and in promising to be all things to all people, anything missed becomes a sticking point and sinks the project.
As popular as it is to say "just solve the problem in front of you", no one actually does that. No one sits down to work on something with one problem - the sit down with likely hundreds, all vying to be solved. Average engineers will look at one, solve it, test it, put it in a text file, push it to prod, and say "look, done!" Good engineers will look at the whole class of problems and recognize commonalities that inform basic architectural and infrastructural decisions.
Also: don't be afraid for dramatic refactoring. If you know it's necessary, do it. It will improve your code base immensely. And the earlier you do it, the easier it is.
And even when you do throw away your code, you're not going to throw away all of it. There are always bits that look good and work well, so you copy them into the new version.
For example, I have a "template" python script that does meaningful things like importing stuff, parses arguments with argparse, does logging with debug/verbose flags, and some other common things.
So starting a "throwaway" project, it's initially over-engineered but that quickly averages out.
Keep your development environment deterministic and instantly rebuildable.
You should ideally be able to take your hard drive out, put in a new one, install a fresh OS, run 1 script, and be completely up and running in less than an hour.
As an added bonus, this means that you can replicate your development environment on any machine very quickly and easily, which makes losing your laptop during travel only an inconvenience rather than a travesty.
I've hacked together some scripts for that here [1], although I really should convert them to ansible or something.
Then you can `cd` into your directory and your environment appears.
The downsides are that there is a steep and long learning curve, and that your colleagues will not understand what you've done.
I also use NixOps to actually deploy my code at the moment, but I will probably be switching to Terraform shortly as I need some tools like AWS autoscaling groups, which NixOps doesn't support.
These are probably helpful:
https://nixos.wiki/wiki/Development_environment_with_nix-she...
https://github.com/direnv/direnv/wiki/Nix
I wrote a script for this too (actually using Ansible), but then everything got a bit out of hand and now it's a framework (as those things go): https://freckles.io
If I may ask, what was the reason for moving away from Ansible and resorting to building something yourself? What kind of issues did you encounter that Ansible couldn't solve?
freckles actually supports multiple backends, but Ansible is the only one that is implemented currently. I chose that mainly to take advantage of all the modules and roles out there, those can all be used from within freckles.
As to it's disadvantages: for some things Ansible is a bit heavy (needs to be installed, and Python available/installed on the target), or not as well suited (orchestration -- although I'm quite happy with what it can do in that space), so the plan is to have other backends (shell, Terraform, direct K8s, etc) that are a better fit for whichever context you are operating in. And of course you will be able to mix and match.
Anyway, if I could have only picked one backend, I'd still go with Ansible. It's a great platform, and it can do almost everything.
One of those, we got a bad batch of laptops. Over a summer five of us had to go through the same thing. The only rough part was the whole disk encryption. That took longer than the rest combined.
this is great if you can pull it off but if you have a lot of disjointed systems plus maybe custom hardware it gets really hard to automate the process. In general I agree though. I also have learned to just live with the defaults of my IDE(s) instead of setting them up to my taste every time.
THANK YOU!
This is not a property that is exploited nearly enough!
This is bad advice. NEVER use timezones with your dates because there is only one timezone: UTC. Anything else is a presentation format.
Of course, if you are in a system that already committed the cardinal sin of storing datetimes with timezones then you have to deal with it. Alas.
For recording all events (i.e. times you see in logs, 99.9999% of timestamps you will see), there should only be one timezone: UTC.
I don't want these people to be forced to convert UTC in their heads when there is no reason for it except for some abstract rule created by someone who doesn't know the first thing about this machine and its purpose, or the people who live there.
They live in the same time zone now. What makes you so sure it will be the same time zone for the lifetime of the machine? More insidiously, what makes you so sure that that timezone's DST rules won't change, leading to timestamps that are correct half the year and then almost, but not quite, correct the other half of the year? Or worse, that are correct for 50 weeks of the year and then out by an hour for 2 weeks a year because the DST change date got moved?
Better to use UTC and get those operators in the habit of converting every time.
FTFY
Why would they do that, instead of using the computer to do it?
time:2021-08-02/09.00.00/Europe/Berlin
time:2021-08-02/11.00.00/UTC
etc
I think that was his point, that you should always care about the original timezone and thus keep it.
Generally you store an offset/name of the zone, which then isnt losing the valuable information about the source zone - if you dont do this you are right, this will fall down almost immediately.
If I live in 2019-06-19T00:00:00+0200 and save the current time as 2019-06-18T22:00:00Z and don't have any additional information, there could be suddenly dragons...
If I say an event is happening at 7pm, I mean 7pm in my local timezone. If I record that upcoming datetime in UTC but I forgot about a daylight savings time change between now and then that doesn't mean the event will happen at 6pm - it's still happening at 7.
I've done some projects where I've stored dates as a localtime, a timezone AND a denormalized, calculated UTC datetime (for lookup/sorting).
This breaks down when you start having to deal with multiple timezones. Then whenever you do sorting or availability checks you have to account for DST/timezones. You will pretty quickly make a method that converts your timezone stored data to UTC. Better to just store it all as UTC so that your DB queries are much simpler.
There are good reasons for not storing in UTC but this doesn't seem to be one of them. Surely the problem in this case is just that you incorrectly converted to UTC? It's just a bug.
Which is why all of the good date-time libraries come with the standard timezone database, which contains a full history of timezone changes. So, (carefully) using one of those libraries, you should be handle to handle any date-time ever.
It really depends on the use case. For example:
* If date is used as a "low precision timestamp", then I'd advise to use full timestamp+tz and extract the date in presentation after applying tz adjustments.
* Sometimes date represents a sort of a fiscal date, which refers to a time period that captures business hours in a certain location. There it is a bit trickier, but slamming a tz on it is not an answer.
As for timestamps, stick to UTC if you can, but in any case keep TZ explicit. There are two many middlewares there that would be happy to assume a tz for you and give you hell.
UTC is great for timestamps (when did this event happen) but not good for user-entered dates. You throw away useful information if you store in UTC.
If the future date was input in EDT or EST you’d know what to do in the future when you convert back to either EDT or EST based on the time of year.
You also need to be aware that timezones change; even now there are discussions about eliminating daylight saving time in various regions. If you store 4:00pm on Nov 25th and convert that to UTC based on the timezone rules as they exist today, you'll be wrong when you convert them back under different timezone rules. There's just no way to do that cleanly.
There are also things to consider: if the user changes their own timezone (by moving) did they intend the value of that time to change (4:00pm is now 1:00pm because it's a WebEx and they moved East) or did they intend that to remain at 4:00pm (it's when they take their medicine every day).
Well, the future has problems no matter what you do.
> If a customer enters an appointment 6 months in the future, it will end up at the wrong time due to daylight savings time.
No, becauae that you store UTC doesn't mean that you also convert entries to UTC using the offset on the entry date without looking at the date the time is attached to.
OTOH, no matter what you store, the fact that DST rules and even timezone boundaries can change over time means that you have to store a lot more about the intent than just the time to be certain of being correct for future times. Did you want X time in Y time zone or X time in the legal time applicable to Z venue? The former is often used as a proxy forthe latter on the assumption that tim zones don't change...
You'll deal with DST hell on your server otherwise. Countries change their DST and timezone policies from time to time, and you don't want to have to deal with keeping track of that stuff or force upgrading your server for a petty politician/timezone issue. That's the client machine's job.
Saving everything in UTC also makes comparisons, logging, abuse tracking much easier.
It depends on the user's intent. If they book something at 4:00pm 2 years from now, will that become 5:00pm if the timezone rules change or stay at 4:00pm. I'd argue that for most future-dated items it should stay at 4:00pm. The only way to guarantee that is store the local time.
Storing UTC doesn't relieve you of timezone problems.
Storing and using a time zone for points in the future is the path of madness. If a user has an event at 2pm six months from now, they almost certainly want that event to be at 2pm no matter what the UTC offset happens to be on that day. This becomes even more obvious for repeating events: an event at 2pm this week will still be at 2pm six months from now, even after DST flips. And even if my government decides to change the time zone my location is in, or decided to opt into/out of DST.
Not necessarily. If the event is a long distance meeting that span multiple timezones, then by definition, the event isn't going to be at 2pm for all consumers of that data.
Location is kind of a proxy for timezone, but it makes more sense to just let the client's OS figure the offset from UTC time than to manually manage timezones in the server yourself.
Similarly, you let the client tell the server what timestamp "2pm two weeks from now" is (letting it consult its OS' timezone database to figure out what that means for the user's chosen geographic location), rather than trying to naively add up the seconds from now to then in the server.
Saving the user's location in your DB to compute offsets on the server is problematic because you can't always get geographic data from the client (e.g. browsers will readily make timezone available to Javascript, but geolocation data might be unobtainable due to permissions/privacy concerns/etc). Also, users may move/travel, so getting geo data from side channels such as sign up forms also could lead to discrepancies.
The problem is really far more difficult and interesting than just storing all dates as UTC as a rule.
And in actuality, even if the event doesn't happen at 2pm for the person who got affected by the DST policy change, it'll still be scheduled at the "expected" time for everyone else (unless they too get affected by a DST policy change as well).
Also, most OSes are decent enough nowadays to update their timezone/DST databases regularly so that UIs that use UTC will reflect the discrepancy in future time intent soon after the policy change takes effect.
You have no way of knowing, just looking at a UTC date, under what conditions it was converted from local time.
By converting a future date to UTC, you're making the assumption that the timezone information you have for that future date won't change from now until that date passes.
> Thankfully, DST policy changes are comparatively rare.
"Russia switched to permanent DST from 2011 to 2014, but the move proved unpopular...so the country switched permanently back to standard time in 2014... Florida legislature and Washington State Legislature passed bills to enact permanent DST, pending US Congressional approval, and California, Maine, Massachusetts, New Hampshire, and Rhode Island have introduced proposals or commissions to that effect... Under an EU directive, from 2021 twice-yearly adjustment of clocks will cease. Member states will have the option of observing either standard time or summer time all year round." -- Wikipedia
> you cannot correctly convert that UTC date back to local time.
Of course you can. It may not be the same local clock time with most implementations, sure, but it will still correctly point to the same physical time relative to the local times of everywhere else on Earth. It would be physically impossible to retroactively change the time of the meeting to account for the the DST policy change and maintain the same clock time for participants in other timezones, so something's gotta give.
It's also not true that you can't recover the intended time after a DST policy change. Although it's monumentally laborious, it's definitely possible: you can keep track of the historical changes in the timezone database and work backwards from the UTC date and the date of that record change.
> By converting a future date to UTC, you're making the assumption that the timezone information you have for that future date won't change from now until that date passes.
Sort of. The assumption there is that the time delta between now and then is smaller than the delta between a notice of policy change and the time it goes into effect. It is, however, technically an engineering trade-off. The trade-off here is that you can have a far simpler and almost-always correct implementation by taking a tiny risk of a discrepancy between intent and actual local time in the extremely rare occasion that a policy changes without advance notice. Responsible governments will typically give plenty of advance notice so timezone databases can be updated and clients can then correctly calculate future offsets since they will know how to account for the policy change.
You could certainly forego UTC time and make an engineering decision of storing local clock time if your application matches a very strict set of timezone boxing requirements, but in my experience, it's more common that people pick this approach out of ignorance than careful analysis, and in some cases, it gets nasty to support when the requirements change (e.g. as soon as you have a new sales team office on the other side of the country)
Storing local time and timezone in the database for future dates is easy. It has none of these issues. It produces the correct results that people expect. You can convert to UTC at anytime.
The thing with the UTC advice and those who don't listen to it is that people often don't realize that both DST policy changes and factors that increase the number of applicable timezones in a business are rare to begin with. They often think that that storing local time is safe because they never had to truly deal with the complexities of representing physical time.
But DST policy changes are much rarer than factors that fuck up local time based implementations, and issues can be fixed without huge schema changes and downtimes. Plus it's less likely you will actually need to fix them since higher-ups won't be able to grasp the issue, whereas a local time bug fix will often come as a demand from some higher-up suit who flew out of state.
The flaw in your logic is in assumption that "intended time" is always fixed relative to one user's clock time. For international events, intended time is physical time. If it's a friday and I verbally arrange a meeting with someone in Sao Paulo at 3pm BST the following monday and that translates to 11am PST today for me at San Francisco, but if there's a DST change for me over the weekend, the intended time is still 3pm BST, not 11am PST, because in such cases, the general logical expectation is that whoever is getting a time change is the one who should be on top of it.
In either case, you'd be able to recover "intended time" (for any meaning of the term) 99.9999% of the time regardless of whether you used UTC or local time+timezone. If there was indeed an unlikely event of an unannounced DST policy change, then whether intended time is wrong depends on who's affected. If it's an international meeting and intended time means physical time, then local time+timezone does not capture true intended time. Likewise, if intended time means local time for a single user, UTC will be the wrong one. But as I said, this case is vanishingly unlikely. A lot of code doesn't handle integer overflows correctly because they are unlikely too and the world is still humming along just fine.
It's not just DST policy changes. If I want to run a report at 10PM every day in my timezone and we store that time as UTC then my report will be running at 11PM after the DST change over, it will start screwing up twice a year. This does not happen when my local time and timezone are stored because the software working with the data can make smarter decisions with more information.
Is it wrong just because the clock time is wrong? Or is the data actually going to get corrupted? In my experience, reports are usually reporting on a period of time, and it doesn't actually matter if the job runs an hour off since you don't want them to be time sensitive in the first place (e.g. you might get noise because some query became very expensive for some reason and delayed a portion of the job by an hour, or the job ran in a data center in a different state, etc). Besides, this isn't really the same class of problems that the "use UTC" advice targets. Crons are a far better tool for daily scheduled jobs. The UTC advice is for systems that communicate dates across multiple machines.
Independent of all that the train still leaves at 2pm local time and I need to be on it.
This assumes particular kind of intent, but not the only kind of intent future times might represent. Datetime + named time zone is often the actual intent (e.g., often for TV schedules), datetime + whatever the then-current legal time is at some particular location is, as you note, also often an intent, but it's not the only possible one. Note also that either of those may not be unique, depending on whether the named zone is offset specific or includes DST shifts, due to DST and other rarer sources of duplicate times in one zone or location , and the intent often is for a unique time, so you may actually need to store even more to capture intent.
As far as I'm concerned, this is the only valid argument against UTC. If a user adds an event for e.g. 2pm March 25th 2022 local time, you convert it to UTC, and then later the conversion rules for that day change, you now have a date that's been improperly converted to UTC.
I don't think it's a strong enough reason not to use UTC in the db, given the multitude of issues surrounding other choices, but I do think that it's important to be clear about the pros and cons.
An instant is a moment in time, expressed in java as milliseconds in UTC.
A ZonedDateTime (if we ignore time portion of it for the sake of brevity) is a LocalDate (year-month-day) with a time zone. One can convert an instant to LocalDate given the time zone, or vice versa. But one cannot convert between the two if the time zone is missing.
So, time zone is not a presentation format, it's an essential part of the data. One can, of course, store a LocalDate in a database, iff the timezone is somehow assumed (and hit the land mine when the assumption is violated by a recent change).
What most repliers seem to have overlooked is that OP is saying that timezones should be used for presentation. Maybe it was not clear, but that also means they should be used for input conversion. But once the dates have to be used by the business logic of your system, you really want to prevent the headaches you will get when you have to deal with timezone transformations everywhere. That really adds a ton of complexity, is easy to overlook and above all an error in timezone conversion can go undetected for a long time.
http://www.creativedeletion.com/2015/03/19/persisting_future...
(besides the year 2038 problem https://en.wikipedia.org/wiki/Year_2038_problem)
1. The time the photo was taken in UTC (as usual). 2. The UTC offset for the photo location.
PhotoStructure stores both the raw EXIF value, a nullable timezone that tries to adopt sibling's timezones (normally only inferable when you have GPS tags, including GPS acquisition time, which is always in UTC). It also stores the source of the inference, so other heuristics know how much it can "trust" that value.
I open-sourced the GPS location and offset inference code: https://github.com/photostructure/exiftool-vendored.js
Of the five companies I have worked for, the two where I was disconnected from leadership were where I felt the least valued (and consequently less motivation). Of the three where I worked directly with leadership, I was treated with significantly more respect and felt a much stronger sense of purpose.
I’ve been doing this for the last three years, but electronically, and it’s amazing. In my home folder there’s a giant text file that’s like my personal log file. It gets a new line at least once an hour, when cron pops up a box and I type in what I’m working on. And for those times when I’m working on stuff that’s perplexing, I write to it as a stream of consciousness — “tried this, didn’t work” “wonder if it could be due to xyz” “yep, that was it.”
I’ve used it a few dozen times to either look up how I solved a weird problem, or why I decided to do something a certain way. I’ve even built pie charts of how I spent my time in the last quarter by sampling it Monte Carlo style.
Which is a pretty well formed solution in this vein, unfortunately written in Perl.
[0] that's closer to https://en.wikipedia.org/wiki/Working_memory
I cannot agree more. (18 years developer here). On those days I just kept pushing, I mostly ended up correcting my poorly-created code the next day. When you know you're done, be done, or at least switch to the lowest mental energy task you can do. Sometimes staring into the screen is that lowest mental task.
For the sake of your eyes (and perhaps well-being), don't just stare into the screen without doing anything. If you have a free moment, take your eyes off the screen and look out the window (or at nearest wall if you have none)
> Documentation is a love letter to your future self
This seems like a brilliant way of transcending what feels like a drudge-job, making it into something really meaningful.
I remember encountering a platform in a language before namespacing was introduced in that language, and it had names for the classes like `Platform_Catalog_Model_Product_Simple_Collection`. It looked really silly, but I always knew what I was working with at a glance. It was honestly a really nice developer experience.
I always prefer a different approach. There is no need to document what a function does, that is self evident from stepping through it. The thing that should be included in the code is the WHY, meaning why does this function even need to exist and how does it fit into the architecture?
Now an accurate high level architecture diagram and function raison d'etre telling you everything you need to know with much less risk of not having an accurate and up-to-date documentation process.
Rarely? I often turn to the documentation to check the capabilities, syntax and semantics of the languages and interfaces that I use. If it were only rarely correct, I would be in trouble.
If you are talking about application code specifically, my experience is that documentation is too rarely present to be an issue.
It depends on what you mean by documentation. If you mean articles on Confluence or whatever, yeah, that's hopeless. If you mean comments in the code, I couldn't disagree more.
I don't buy the common argument that comments easily become inconsistent with the code. I can't imagine changing a line of code and completely ignoring the comment above it, and I'd definitely require a fix if I saw someone else do that in a code review. That's just a ridiculous level of sloppiness.
> There is no need to document what a function does, that is self evident from stepping through it.
I'm afraid I disagree here too, though again conditionally depending on what you mean. Straightforward logic shouldn't be littered with superfluous comments. But on the other extreme, the fast invsqrt algorithm needs more of a comment than the canonical "what the fuck?"
Yes, given weeks of head scratching any arbitrarily-dense block of code will release its secrets, but that's not good engineering. Anyone can build a bridge by dumping a hundred billion dollars of concrete into the bay until it's dry; good engineering is doing it cost-effectively. A well-placed comment in some non-obvious logic can save time for a developer (likely you) every time they interact with it.
> ... Yeah, you don't like that hushed solution 'cause you care" was one of the nicest things someone told about myself.
This is very similar to a foundation that we repeat at work. Everyone is trying to make things better, we might not agree on the how, but we must agree on the intent. When you assume good intent, conversations go much, much better. It can be a great way to frame disagreements. "We are disagreeing here because we care."
Though I've found this helpful in expanding what I can do, or how well I can do it, what I find using a different language at home than at work does best is prevent burnout.
I can be reckless, and play with the language, because I don't have the tired patterns of my day trying to rigidly enforce best practices and cover all edge cases.
My mind can relax and do the wrong thing if I want, and I don't have the autoformatter or linter in mind. I don't have a dozen rules that will spit red errors if I step out of line.
Those things are all good for work. The application must be solid, and the user deserves stability.
But when I'm hacking together a game for myself that I never intend to release, I just want to experience the fun side of programming without the hard part.
"Future thinking is future trashing": we had a good engineer with a bad tendency to write complex generic code when simpler specific code would work. In one case, he took a data structure and wrote a graph implementation on top of it - it was only ever used once, by him, and I recently rewrote it in about 15 lines of structure-specific code. In another case, he wrote a query builder to make it easy to join data from a new service with our legacy DB, which makes the high-level code simple, but the full implementation much more complicated, and means incoming engineers can't obviously see what implementation to use. His last week at the company was... last week.
"Understand and stay way of cargo cult": Today, I was reviewing someone's code, when I saw a language construct I didn't recognize, and which made no sense to me. I asked him what it was meant to do, and if he could give me a link to somewhere which described the construct - he said no, he'd mostly just copied it from someone else's code. I asked 'someone else', and he pretty much said it was useless, he's worked in so many languages that he gets confused about language idiom/construct sometimes, he writes his code like he'd write a letter to a friend, and it wasn't like anyone was reviewing his code at the time anyway. I said I wasn't going to take his stuff out of the existing codebase, but I wouldn't let anyone copy his useless code in future, and linked him to https://blog.codinghorror.com/coding-for-violent-psychopaths...
D not only has it as a library, it's a core feature of the language. As incongruous as it sounds, builtin unittests have been utterly transformative to how D code is written. Built in documentation generation has had the same effect.
This could make great bulletin board material in some other format, but there's just too much here for that.
I started my reply by cutting and pasting the gems with my little follow-up, but quickly realized that would take all day. There just so much good stuff here. So instead I narrowed it down to just one:
Code is humans to read. ALWAYS. Optimization is what compilers do. So find a smarted way to explain what you're trying to do (in code) instead of using shorter words.
This really strikes a nerve because in 40 years of programming, most of my problems have not been with building code myself, but with trying to figure out what my predecessor had written. I bet I've wasted 20 years of my life refactoring the crap of others who would have benefited greatly by reading posts like this one first.
There's lots to think about. Lots to smile and agree with. And lots to disagree with (OP reminds me a little of myself, a caveman who often claims to know "the one right way" :-)
If you don't have time to read this now, bookmark it and come back later. One good idea from this can make a big difference.
I also curse a lot about code my predecessors have written but I bet my successors will curse at my code too :-)
> You'll find people that, even if they don't small talk you, they will bad mouth everything else -- even some other people -- openly.
So true. It has happened to me so often - actually at most brown-field projects - that when I get onboarded, someone is telling me in which bad state this and that is, even how bad this and that person is supposed to be. Although I tend to be optimistic, when the project seems to be nicely challenging at first (not only from the code), I somehow take over this negative attitude for the rest of the project.
I think people should be able to informally talk about projects, even gossip sometimes. But it has its limits, especially team/project leads should IMHO be really careful about taking negative stances on certain projects/people/teams. In fact the limits to mobbing are fluid.
For me this is the single biggest annoyance in any job.
I certainly don't want to encourage any sort of toxicity like this. I will say, however, that I recently started a new job. People haven't been particularly welcoming so far but I finally managed to bond with someone over a discussion of the skill gap that exists between some of the employees and the lack of quality code produced by a few of the stubborn veterans.
It wasn't just a bonding experience either. I was able to learn more about the nature of the organization and how I can help to improve things in the future. It also had a calming effect because I was originally pretty intimidated by some of these veterans but now I know they're just flawed humans like the rest of us.
Anyhow, complaining doesn't help but trying out things and gradually improving. In fact it's possibly even a good opportunity because nobody else wants to touch such code so there's room for creativity (of course within the limits that the stakeholders are fine with).
Example: how can we improve on the design of the bicycle, but keep it simple? You could add expensive composite materials, design a new gearing mechanism, create new complex geometries based on wind tunnel experiments... all of those are more advanced, but complex; simple is a tough nut to crack.
What about in languages with named parameters? I think keyword arguments solve this issue pretty well.
If the name is optional, as it usually is in eg. Python, then there is a temptation for the writer to omit it, which means the reader won't know what "True" or "False" stands for.
Are there languages besides Smalltalk and its descendants with mandatorily-named parameters?
def f(*args, **kwargs):
and then making sure any params you use in the function come from kwargs, and raising an error if args is non-empty.Python 3 has a nicer way of doing the same thing, that I can’t remember.
(Sourced from the book “Effective Python: 59 Specific Ways to Write Better Python”.)
>>> def a(b, *args, c): print(c)
>>> a()
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: a() missing 1 required positional argument: 'b'
>>> a(3)
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: a() missing 1 required keyword-only argument: 'c'
>>> a(3, c=5)
5
It's also valid to define it this way, if you don't need the * args: >>> def a(b, *, c): print(c)In this example the call-site could instead look like `getUserMessages(userId, MessageStyle.SUMMARY)`. Naming the enum is always harder than naming the boolean but that's kinda the point.
I'd question this specific case in a different way, though: summary vs detail sounds like different object types, which means different return types and so there should be two different methods (eg: getUserMessageSummaries(userId) and getUserMessageDetail(userId) ). It's perhaps a bit more work up front, but when consuming this you don't end up with a bunch of properties that may or may not be null depending on how you called this (instead, they're just not there on the summary response), and in a typed language it will simply fail to compile (making your mistake very obvious).
I think named parameters don't actually help, because the underlying problem with the boolean (aside from the mystery about what it means when looking at code calling it) is that it implies a potential separate "mode" of operation for the function. That the single function might actually serve two different purposes. It doesn't always imply that I imagine, but it's pretty likely.
I'm guilty of this in my code, and I know that my code quality has suffered for it - for some reason, the way he put it in this article gave me a moment of introspection there, and it's something I'm going to try and take away from it and improve my own code with in the future.
Note that sometimes I add an optional boolean parameter to avoid breaking existing method calls. The default of the new optional flag is the original behavior. It's a case being backward compatible versus changing more code in order to fit a revamped interface.
Is there really a point in creating Enum or writing more complex internal logic if you use proper IDE?
Only benefit I can see is doing CR's via Github like system - but still, I do not believe in CR without actually pulling, viewing, and testing code on own computer.
Is boolean flags antipattern? Well, this all depends on the workflow. I follow ideology of making thing work first, then making it perfect. If introducing flag saves me two hours that I can spent to deliver on time - hell - why not - knowing that everyone who works with me uses some modern text editor / IDE - they will know immediately what's the parameter is doing. Also, what I've observed is that most of the people do have a habit of `fn + click` into function they've never seen before - so they will learn about parameters one way or another.
Cheers
Which is super useful and easy to read.
getUserMessag({userId:1234, retriveFullMessage:true})
https://codeburst.io/es6-destructuring-the-complete-guide-7f...
this one is priceless.. I had so many arguments in my past with cocky devs about this topic. Don't get me wrong, patterns are good, but it's only a small part of coding. Also patterns aren't there to follow them as they where the law, but bend them to your needs. One doesn't even necessary needs to be implemented to 100% as it is in the "book". So yes, this part of the post i'm very fond of, it's really wise.
Patterns are a way of describing code. They are meant as a common language to use when explaining a codebase to another dev. If you are writing code with the express intention of "using X pattern", you're doing it backwards.
> don't add flags/Boolean parameters to your functions.
Many languages have strongly-typed enums for them, makes calling code readable, e.g. getUserMessages( user, eMessageFlags.Full ). Especially useful for more than 1 flags.
> ALWAYS use timezones with your dates
UTC usually does the trick?
> UTF8 won the encoding wars
UTF8 used on the web a lot, but OP seems to be generalizing their experience over all programming, not just web development.
> when your code is in production, you can't run your favorite debugger.
No but I can run my second favorite, WinDBG. A crash dump .dmp file is often more useful for fixing the bug than any reasonable amount of logging.
> Optimization is for compilers
Compilers are not that smart. They never change RAM layout. I've recently improved C++ project performance by an order of magnitude replacing std::vector everywhere with std::array (the size was fixed, and known at compile time). Before I did, profiler showed 35% time used in malloc/free, but the cost of vectors was much more because RAM latency.
A sufficiently expressive type system can check your logic for you. Push as much of your propositions and predicates into the types as possible.
Data types are better than exceptions, return codes, etc for the majority of unexceptional cases. Conditions and restarts are better than exceptions. Sometimes crashing is not an option when your computer is flying in the air with a human.
In addition to specifications -- often a bunch of boxes with arrows will not be sufficient. There are a range of tools to check your designs from automated model checking to interactive proofs. When the problem is difficult to understand on its own then the solution is probably even harder -- a good sign you should consider using one of these tools to make the problem clear and guide you to the right solution.
I really think comments can be especially valuable. They're valuable when reviewing code because they tell the reader what the developer was attempting to do, not necessarily what they did.
Not everything is expressible in every type system. Even something as simple as "this reference can't be null" isn't expressible is many languages.
Comments are easier to read than type names, a sentence is much more expressive than code. Especially code that is tricky due to performance or domain complexity.
Type and method names can rot too. If you have a method that starts with get that someone adds a little bit of updating logic to making the method name misleading.
And comments can be kept up to date easily by just adding an item to your code review checklist "Ensure comments match code".
It varies on the level of expressivity and soundness of the type system employed as to how valuable comments are. In my experience there's an inverse relationship: the more expressive the type system, the less valuable are comments. This is true in so far as the types get richer the more they can express with less ambiguity than prose. In a way this makes the comments that do exist in such a code base even more valuable since they describe far less and are probably filled with interesting information such as why certain propositions are important, etc.
However when you're working in languages without a static type system or a rather weak one, comments can be necessary and invaluable.
Update
To clarify, in a type system as rich as a dependently typed language, given an inductive definition of vectors, and a type; Agda, for example, can often find the solution (the term) that implements the type. You could add a comment if you wanted to but you'd only be elaborating on what is already obvious by the type signature.
This does scale beyond trivial examples such as vector products and into the realm of business logic; it's possible to encode state machines this way. Code comments of this kind of code end up looking superficial.
In a more common FP language like Haskell, often derided (even by me) for it's lack of commenting and documentation, seems to be that way precisely because once you get used to understanding how to reason about types... well it gets redundant... and the compiler cannot check that comments are correct with respect to the term level code. The comments are sparse and rich instead of redundant and full of exposition.
That's not to say that comments don't exist! Other poster have given many good examples where they are definitely necessary.
I ascribe to the rule that there's a balance and too many comments are not helpful just as too few are as well.
> When you start the code by writing the documentation, you're actually making a contract (probably with your future self): I'm saying this function does this and this is what it does.
> If later you find out that the code doesn't match the documentation, you have a code problem, not a documentation problem.
"Self-documenting code" is, in my opinion, a silly canard. Maybe it's appropriate for some brain dead glue logic or frontend boilerplate, but any time you really need to think about what the code is doing, a comment will be immensely helpful. I don't at all buy the argument that comments too easily get stale. They won't get stale if you maintain them.
A comment like "gets the details about a user" is of course a waste of time.
What I like to see is everything about using it, without having to read the code. For example: "Gets details for a user, based on the Service.CurrentUser access rights. If userId doesn't exist, is not available valid id, or the current user diesn't have permission, throws exception. Note this can return data for disabled and deleted users, so if used for non-admin functionality, you should check the State property. Due to caching, this might not show changes made in another session in the past couple minutes."
The lack of a check for disabled status (for example) won't be obvious if I'm not actively thinking about checking disabled status. That few seconds of a comment can easily prevent a bug when someone consumes this - especially if they didn't write this API or are new to the cosebase (maybe they don't even know disabled users are a thing).
Sometimes when you write these types of comments you realize your naming is bad, or that there's an API change that would make it easier to avoid a mistake - such as having separate GetActiveUserDetails() and AdminGetUserDetails().
I think about this during vice reviews, too: I've often asked for comments to be added on existing methods being consumed (but not modified in the code I'm reviewing) because the new use made me wonder about how something would be handled and I had to go read a couple levels deep to figure it out.
getUserDetails :: UserID -> DBAction (Maybe AnyUser)
This type says a lot already. The definition of `AnyUser` would specify the parameters that the comment only says in prose. Or we could be more specific getActiveUserDetails :: UserID -> DBAction (Maybe ActiveUser)
We could also include the authorization specification in the type constraints on the `DBAction`: getUserDetails :: (DBAction m, HasAdminContext m) => UserID -> m (Maybe AnyUser)
This is what I mean when I say that a rich type system can enforce a lot of what you might traditionally use comments for in a weaker type system. Now my hypothetical comment might highlight some salient points about this function, such as performs N queries to validate all of the available authorization contexts, use with a user role that has a limited number of contexts for best performance.Even something as fundamental as floating point types need documentation, which is why we have IEEE 754.
Not saying Medium is perfect, but it does help standardize the display of all these random, one-off blogs.
I agree, for a much wider (programming) audience, Medium would suit better for this blog.
In my opinion, the style of the blog is as important as the content because we are dealing with the self-expression of a person here. The colors and fonts were conscious decisions, and presenting the data in the most efficient and standardized form is not the first priority.
(Also calling it a "one-off" blog is kind of mean. Sure it might the first and the last time you ever read a post there, but that blog has more than one hundred posts, so it's not a "one-off" for the author.)
It's fine if you don't like it, but then say that.
Say "I didn't like the typography and colors, I prefer $style" if you really have to, but still, why?
Bright on dark, fixed width, sans serif.
Pretty much the colors and typography of programming from the first terminals, and with some interruptions, lasting into the modern day. Noting the audience, the style could be said to be fitting, especially with the current retro trends.
> Lisp did this a long time ago, and now most languages are getting it too.
I'm not sure if this is correct. You can probably write a macro for lazy eval, but by default lisp is a CBV almost-lambda calculus.
Or you could pass around quoted things, I guess
Possibly the closest thing available in Common Lisp are Lisp macros - they receive their arguments unevaluated, and are free to utilize that input for further computation.
_cries in kubernetes_
> (A dickish move you can do is to create the new functions, mark the current function as deprecated and add a sleep at the start of the function, in a way that people using the old function are forced to update.)
This idea is sure to waste lots of people time, lead to angry emails, and is probably worse than just changing the function signature in-place without any deprecation schedule at all.
The big one is: start with the specs, then code. I used to think that, wasting time making plans for an end result that ends up completely different. Now, I prefer to start coding right away. Anything goes, it just has to be code.
Why code? That's because it keeps my feet on the ground. And because it is the early phase, I can still change things. Chances are that most of that early code will be scrapped, maybe several times. No big deal, I may have lost a few days of coding, but I have a much clearer view than if I spent that time writing specs.
As for the documentation vs code, I spend all my experience points into reading and understanding code. I sometimes even force myself not to read the documentation, going straight for the code instead. Comments are lies. For me, documentation will always be an afterthought, for others. For me, I have the code, and if I have trouble reading it later, that's because it is bad code, not because it lacks documentation, and I try to learn my lesson, and maybe rewrite the offending parts.
For the social part (code of conduct, micro aggression, etc...), my strategy when things become unpleasant is to go technical. The bigger the asshole facing me is, the more deeply technical I go. Here two things can happen. Either the asshole is incompetent, and because he doesn't want to look too much like a fool, he usually leaves me alone. The other is that the asshole turns out to be really good, and in that case, the interaction may not turn out to be the most pleasant, but at least, there is something to learn.
I would personally rephrase this as unit tests are necessary, as are integration tests, as are end-to-end tests.
The testing pyramid: write most of your tests as unit tests, then write a bunch of integration tests, and finally throw in a few end-to-end tests.
> Tests make better APIs
Yes! This is such an important point. A perfect example I love is that globals and even thread-local values make testing hard, and that’s reason enough to avoid using them.
> Make tests that you know how to run on the command line
Again great advice. I would also add, get your tests running as early as feasible. This saves you rewriting APIs and other decisions that might make testing, even deployment (which you need for end-to-end tests), significantly harder.
Anyway, this entire set is a great read, and I agree with much, but not all.
http://www.ox.ac.uk/sites/files/oxford/styles/ow_medium_feat...
You need a thin layer of high-level tests (controller and/or browser) covering the entire application, and then columns of lower-level (integration and unit) diving deeper into it where there are areas of particular complexity or risk.
An example: Let’s say you’re writing a DNS resolver.
You need to parse all of the various RDATA portions of records. It’s far easier to write an exhaustive suite of unit tests that verify the code is parsing those data elements properly and throw in many tests for edge-cases that might mean insecure software if done incorrectly.
But it’s absolutely invaluable that we also have integration tests that verify an entire Message parses, and even moves through the entire stack from parsing to response. But why construct an entire Message when all you need to test is the RDATA? It’s easier and less complex test code to localize the RDATA test, and then to do a smaller set of Message based tests that verify the message moves through all the stacks properly.
Then popping all the way up the stack, you actually need to generate network traffic and send real DNS queries and verify that those work. That means multiple processes in your end-to-end test. Trying to produce a full set of tests that would validate individual edges cases in all RDATAs would be an extreme amount of redundant code, and also would fail due to many other reasons not related to an RDATA change, thus making it more time consuming to track down a failure.
Each area of tests is absolutely important and all are necessary for different reasons.
Integration tests would mix multiple modules.
Depending upon the specifics of your application, unit tests at a lower level than the module level may be suitable. E.g. complicated algorithms a module might internally rely upon.
Our language around testing is missing a slot. We have unit, integration, and end-to-end. Should be unit, module, multi-module, and end-to-end. Depending upon the specifics of the application, you might have more unit or module level tests.
A great semantic insight.
I still prefer to test complicated algorithms via the external interface. It does end up requiring some scaffolding code for generating inputs based on test cases, but code is for automating repetitive tasks, and they're pretty fast these days, so it's rarely a problem, even for many thousands of test variations. It enables you to test the interactions between your various unit-level variations, which is more likely to catch errors. Again, it also helps make your tests visible, along with the edge cases you're testing, and many times developers working at the unit test level are way more thorough than they actually need to be. Couple this with efficiencies around refactoring and it can actually save time.
... except I think it's perfectly fine in languages that support named parameters. In fact this isn't even specific to booleans. Say we're using Python,
draw_rect(width = 5, height = 10, top = 5, left = 6)
is much more readable than draw_rect(5, 10, 5, 6) draw_rect(5, 10).at(5, 6)
draw_rect().from(5, 6).to(10, 16)
It's one I'd be careful with though, as it can be easy to end up with something less readable.Amen to that.
I thought I was old-fashioned because I keep one of those spiral notebooks on my desk. It's invaluable. I've never been able to take quick notes using a computer.
When I fill up the notebook, I run it through the scanner, toss it and get another.
yes :-)
I wish I could make my boss understand the importance of this one.
In his mind, the best coding strategy is:
1. build prototype
2. add feature
3. goto 2.
Nothing quite like experience to make you wish you could go back in time, with all your current knowlege, and take over the world.
> One commit per change Amen, brother.
> You're responsible for the use of your code That's a tough one. Code is a tool, like any other. If you're making a gun, then you have to accept that your product will kill people. But if you're making a hammer, it's not really fair to hold you responsible when someone goes postal with it.
> Learn from your troubles This. So much this. If you're not doing this... the rest is worthless.
> This is what most people get wrong about adding booleans to check the number of True values.
This appears in two different sections. Who are all these people adding booleans as if they're integers? It's like someone wrote a whole treasure trove of sensible life advice that included "this is why most people have trouble when they use a dry-erase marker as a toothpick."
get_message(full_message=True)
Is this an OK pattern for Python but not some other languages, or am I writing more confusing code than I had realized?get_full_message and get_partial_message or something like that
The key here is the reader being able to tell at a glance what the args are/do, without needing to hunt down documentation.
e.g. getUserMessage(userId, fullMessage=true)
While that can help, I'd still argue against it unless your language has some mechanism to force use of name. Because if you can omit the name (i.e. getUserMessage(userId, true)) then someone will omit the name.
Another D feature that is so simple in concept but miraculous in its effects - a "deprecated" attribute that can be applied to declarations. It enables plenty of warning given to users before it is removed.
It's a pretty good counter to people who think you should always use -Werror. One wrong deprecation could take you a month to fix!
There's a XKCD comic which points out how people who claim to "hate drama" tend to be the most dramatic people[1].
That's how I tend to perceive projects that put a lot of emphasis on their CoC. The more they emphasize it, the more I expect pettiness, vindictiveness, and bad faith.
But the important thing isn't the rules, it's that there's someone to enforce them. Ideally, the rules exist to constrain them, not you.
> Anyone who objects to that probably has never worked at a large company.
I can assure you that's false.
Unless you have procedures.
Also, the 'don't add boolean switches' doesn't count for Powershell, as you can use named switches that are false if not added. For other languages you could add constants
> Toxic/micro-aggressors are only fixable if they are YOU
This is a good thing to keep in mind. But the article doesn't provide any suggestion when you are a victim. Anyone has tip(s) to deal with a micro-agressor?
also re: Gherkin: Just don’t cucumber. If you focus on integration test technology like Cucumber or headless browser drivers, they will absolutely balloon your test suite runtime and it is partly because they are extremely inefficient (testing the same things over and over and over again such as “set up a logged-in user with 3 assets” for 90% of the test cases in the suite). Avoid. Maybe not entirely, but in large part.
It's also riddled with typos which made reading it hard. There's some wisdom in there, but it was clearly written in an editor without spell/grammar checks. Maybe there should have been a section about rereading your own work before sending to your team/prod? =)
A lot of people use English as it's the language of tech, which means that the potential audience is significantly larger.
Brevity shouldn't be a virtue of coding. The best code is self-documenting code and self-documenting code is rarely brief.
To quote Wordpress PHP Coding Standards: In general, readability is more important than cleverness or brevity.
https://make.wordpress.org/core/handbook/best-practices/codi...
Between unit tests (fast, unrealistic, tightly coupled, catches fewer bugs) and integration tests (slower, more realistic and catches more bugs) I virtually always pick the latter.
I'm happy to trade CPU churn for "catches more bugs". CPU time is cheap.
Ideally you’ll have lots of unit tests and a few integration tests, all valid.
I’m scarred by seeing test suite runtimes (THE most important element of programmer productivity IMHO) completely balloon after focusing too much on integration tests. There is a world of productivity difference between a 1 minute test suite and a 40 minute (or 4 day) test suite
It is a dichotomy. You do not want the same thing being covered by two different types of test. Duplicated test coverage means duplicated maintenance.
>There is a world of productivity difference between a 1 minute test suite and a 40 minute (or 4 day) test suite
Not in my experience. Tests can easily run while I'm at lunch, overnight, working on something else or, if time is really of the essence, in parallel. If I'm running an individual test while developing anything that runs in under a minute has no effect on my productivity.
Tests that don't catch bugs are a far, far bigger deal.
Insinuating incompetency when choice is clearly a reasonable explanation is in my opinion quite disingenuous.
If you don't like the style, at least say so clearly, if you have to. But why do you have to?
The authors tip to disable comments, I fully understand. I find writing hard, and I could never cope with all the things - except the actual text - people find the need to complain about.
> If you don't know what you're trying to solve, you don't know what to code.
I like this, but I would add "Always read the spec before you code".
> Future thinking is future trashing
It really depends on what you're building, and how long term the project will be around.
Git vs Javascript Frameworks
getUserMessage(userId, True)
in Python can become
getUserMessage(userId, getUserMessagesFull=True)
ah, he must have forgotten about named parameter
i.e. 1rem hs would be ok if you'd had 0.8rem ps, and conversely you'd have the same issue if both were coded to be 2rem
and then later
> Be ready to throw your code away
Then why would it be useful to have the spec first if you're expecting to throw it away anyway?
Better combine this advice,
> Write steps as comments
with a higher level language, and write code instead of comments, and have fun developing what you think is the product you need. You'll see plenty of corner cases, learn tons of new stuff about your domain that you didn't even think existed and then you'll want to start over since many of your initial assumptions will be wrong anyway by that time or you'll see new extra features that you couldn't have possibly discovered without actually having a toy product to play with and try new ideas with.
Tests? No, definitely not on the first run. Maybe only for documentation purposes in legacy systems. But don't take away all the fun by starting with tests. You'll paint yourself into a corner.
This seems like 30 years of experience in a corporate environment. I would take these recommendations with a grain of salt. I would venture to say that whoever starts with "I have x years of experience" or "I'm an Y language developer" is mostly bulshitting you or doesn't really know what he's talking about, so he'll need to have a badge to shove it up your face to demonstrate his qualities.
Well... That's where the money's at, right?
> Tests? No, definitely not on the first run. Maybe only for documentation purposes in legacy systems. But don't take away all the fun by starting with tests.
Kindly disagree. In a corporate environment (at least the one's I've worked so far), you HAVE to start with the tests. From my experience, people maintaining your code after you've left the company (or you are sick, whatever) will be grateful to have a testsuite that can verify the edge cases are still working after their changes have been deployed.
Also, when the first working lines have been written, there was no way we would revisit them to write test on top. I need tests to verify that what I've just written actually works.
Others might create a "ExampleConsoleApp1" for this, but then I can also write a test.
> people maintaining your code after you've left the company
That's what a legacy system is. I agree. That's a good use of tests. But don't start WITH them. You'll build rigid systems.
> Well... That's where the money's at, right?
Agree with this also. But this is (or should be) hacker news, so we look for better ways to make software, regardless if it makes money or not. Or maybe we should rename it Financial Times?
Most people write terrible tests, which are over mocked. You should test input(say simulate http post call) then check the thing is in the fake database or even better a call to retrieve the item. You can completely change the way its implemented in between.
You're still using mocks in that case. You can avoid using mocks entirely by decoupling your tests from external dependencies. That is, instead of making a HTTP post request to the application, test a method that takes the content of the HTTP request as a parameter and assert on the return value. For the database, you can test a method that takes parameters and returns the appropriate query.
Then integration tests can be used to test the interactions between different parts of the system.
The http post is the input into OUR system under test, not an external dependency. Nothing is mocked. I find going through the entire http pipeline catches misconfiguration issues.
"For the database, you can test a method that takes parameters and returns the appropriate query." - That's what the fake database does. Implements the interface to the database. In this case implements a fake query method. Nothing is mocked. Stubed but not mocked.
Agree. The key to good tests seems to make them neither unit tests or e2e tests, but something in between. API tests if you have an API, or "medium sized module" tests in other systems.
Unit tests and e2e tests have value too, but they're a lot more work to maintain for less value IMO.
It depends. If you'll test implementation - sure. But if you test use cases - not necessarily,
The idea is that you should be ready to throw your code away if the requirements change. If the spec changes (e.g. your own made up spec to the client's actual spec) then be ready to throw away some (all?) code.
Trying to squeeze in the tests into existing system is painful. So better do the tests before it had become a "legacy system to document".
Tests should be written before and during writing the new system.
That's because they are the executable language for the results you want to get from the system. The two other options are: use plain text to document your manual actions and expected results, don't document your manual actions and expected results.
This, but also if you code first then write tests you try to write tests based on your interpretation of how the code you already wrote currently works, when its better to write tests to outline and define how the code _should_ work in the future.
>You'll paint yourself into a corner. I find it is the exact opposite. If you write all your code first, then write tests, you will inevitably find a bug and refactoring working code to fit a test is infinitely harder than adding more tests or replacing irrelevant ones.
Know where you are.
A better approach is to demonstrate your skills and why it's important, then let people ask and wonder about your background...
Basically, shoving experience or qualifications in my face doesn't make me respect your point of view more, it just makes me think you can't be humble and want to make yourself look good.
This is actually great advice for both people with experience and people listening to them.
Depends what you're doing. For interactive programs, automated testing is more painful than just firing it up and testing manually, at least at the beginning. For noninteractive systems it may be much easier to start with a test as a spec: "parse this file and produce this output".
My workflow is basically:
- Plan the project out, but not super detailed, this is more like a high level overview of features. I did a 90 minute video live on this process once[0].
- Think about each state of what a feature could be in. Here's another video[1] showing that process.
- Design some pages around your states (if you planned a user registration system, start designing the register form)
- Dive into the code and implement one of the features (using pseudo code to help with the flow initially if needed)
- Write some tests to demonstrate that what you have works as you intended
- Heavily refactor your code if needed, leaning on your tests and refining them as you come across edge cases, etc.
Honestly the above never fails for both small and large projects. You always have something small and focused to work on and the code you produce typically ends up being in good shape by the time you reach the end of the workflow. It's a fast cycle too (easily multiple loops per day) once you have the states ironed out.
[0]: https://nickjanetakis.com/blog/live-demo-of-planning-a-real-...
[1]: https://nickjanetakis.com/blog/design-your-web-uis-faster-by...
> Legacy code is code not under test
That said, I'm not against the existence of legacy code, but any bugs damn well should be written up as tests to catch regressions.
That way new development can be done without tests (full TDD is tedious and often way too coupled to implementation imo) but any bugs are captured in a way that can't be tests written against the implementation and guard against regression.
Hard disagree on that, there is nothing more annoying than reading code without plain comments, it's always less obvious than the original developer thought and the worst is that it always happen to your own code