I majored in CS a long time ago, and have worked as a programmer since. I write primarily in C and C++, all very old school. Nevertheless, what I have to say here applies to programming across the board.
In this talk I'll speak more broadly at first, then get more specific as I go on. I'm going to start out with some general observations about being a programmer for hire, and the state of the whole high tech industry, then talk a bit about high-tech companies from the point of view of an engineer, then get down to brass tacks of actual coding.
Raise your hand if you are reasonably proficient at at least one programming language.
Raise your hand if you are actually considering a career as a programmer.
Also, assuming you like programming and have a knack for it, it is a honorable craft. You just can't do it unless you have skill: natural ability coupled with training and practice. In today's world, that's just not true of all jobs. You are being paid to practice an actual skill you have, like a woodworker or a circus acrobat. Yes, attitude and personal hygiene and stuff like that count, but mostly you are being paid for this real-world thing that you can do that most people cannot do.
On the other hand, it is still plagued with a gender disparity problem, which is not good for women, and not good for men either. It is also plagued with a racial bias, and while we can argue and discuss all night about the roots of this, the fact of it can't help but be an embarrassment to the field. I hope it is fixed, and soon.
As a young programmer, you tend to think in terms of code. I program, and I do it well, and that's my focus. We programmers tend to be nerds, somewhere on the asperger's spectrum, head-down. But don't think the interpersonal, departmental politics don't affect you! Yes, worry about the mundane business stuff: org charts, revenues, who are the customers, markets, is the company growing or not, etc. The fish rots from the head down. Sometimes it is only in retrospect that you realize that the company was doomed because of bad management, no matter how talented you and your coworkers and immediate managers were.
I'm old enough that I've seen the culture of the industry change perceptibly in my career. In the 70's and 80's, there was the tradition of the Xerox PARC hippie programmer, or at least lots of people who came to programming from other fields. It hadn't been professionalized yet. The subsequent professionalization has its good and bad points. In terms of the culture of high tech, it has lead to a narrowing of the range of types you tend to run across in programming jobs. Later on, not only were there CS majors at college, but in the dot com boom and bust, the culture of programming got intertwined with a sort of get-rich high-tech startup culture. I'm not passing judgment, that's just what I've seen.
As far as the long term prospects for programmers are concerned, you tend to hear two very different things. First, people are always complaining that there just aren't, and won't be, enough skilled techies to go around, and that the industry is starved for engineers.
On the other hand, you hear that we're all doomed because any company can hire three superb engineers in India for the price of one American engineer.
There are grains of truth and grains of salt in each of these. Yes, there is always a need for skilled programmers, but the schools are churning out lots of them as well. And the whole outsourcing thing has, I think, peaked and started to subside. Outsourcing, especially to India, used to be the silver bullet that everyone wanted to use some years ago. However, it is hard to do right. It is a fantasy that you can adequately manage a project from half a world away. I think the hidden costs and risks of outsourcing have become more widely recognized since then, and for now at least, it looks like as long as there are American high-tech companies, there will be jobs for American engineers.
If you work in the private sector, that is, for a company that seeks to make money as opposed to the government, one major distinction worth understanding is that between publicly traded and privately held companies. Publicly traded companies are those whose ownership takes the form of stockholders who can buy and sell shares of stock in the company on the public markets. There are all kinds of laws and regulations that compel publicly traded companies to disclose financial information. Believe it or not, this information matters to you as a prospective employee. Is the company making money? Is it growing in terms of profits and revenues? Is the headcount growing? Would you invest in this company? They say that in Las Vegas, they pump pure oxygen into the casinos to make everyone feel better and gamble more. Working for a company on the upswing is like that. Working for a company that is slumping has the opposite effect.
A privately held company is one somewhere between the stage of three people hatching an idea over drinks in a pub one night, and the IPO (initial public offering, when your company goes public). Start-ups get their money from venture capital firms, who then own the stock in the company. Most start-ups fail, and the VCs lose their money, but the ones that manage to go public, or more likely, get bought out by an already-established company, do so well that the VCs more than make their money back.
Let's say the three people with an idea pitch it to the VCs and get $10M of seed money. That is called round one. If they burn through that but aren't profitable yet, they must drum up more VC money in a round two. And so on. If you are considering working for a privately held start-up, you must ask how many rounds of financing they have been through, how much money they have left, and what their burn rate is. Just to see how nervous and evasive the person gets who answers you, if nothing else. If they've been through four rounds, and are nowhere near break-even (i.e. they are losing money) this is a sinking ship.
If you join a company, whether public or private, you will probably have an opportunity to buy stock options. This amounts to the right to buy company stock in the future, at a price that is established today. If, as we all hope, in the future the price is higher than today, you are guaranteed to make money on the deal. In the case of a start-up, sometimes a lot of money.
This used to be everyone's fantasy (and to some extent, still is): work like a dog for a start-up for three years, and retire rich off those stock options when the company goes public or gets bought out. During the huge dot-com boom of the 1990's, this was seen as the road to riches. Of course, tons of those companies went belly-up and no one made a dime. Nowadays, people aren't quite so eager to work 18-hour days, 7 days a week in what I call the churn-and-burn model. The industry has matured and become more realistic.
Any experienced job hunter can tell you that personal connections are the most valuable asset you have. Exploit them. You know people. Be open to serendipity. After your first job or two, you will know lots of people in the industry. They will have moved around, and will be working at other companies after a while. It is more likely that you will get a job through someone you know than you will by cold-sending your resume out, or going through a job-search web site.
Keep in mind that companies almost always need good people, and they know that it is likely that their existing employees know some of them. For this reason, they give a finder's fee to any existing employee who brings in the resume of someone who ends up getting hired. Therefore it is actually doing a friend a favor by asking for a job at his or her company - they could get a tidy bonus of a couple of thousand dollars. It is worth noting that companies pay about ten times more to headhunter agencies for the same resumes, and often end up with inferior results. Personally, I think this is stupid. They should bump up the internal finder's fee, and really motivate their own people to find more good people.
I don't use Facebook, but I use LinkedIn, and you probably should too. Before it existed, I kept a simple text file on my computer, in which I jotted down the contact information about old coworkers I wanted to keep track of. LinkedIn is much better. It is like Facebook, but with no pretense of friendship - just business contacts. You get to keep track of all the people you have ever worked with, and see at a glance where they have worked in the past, and where they are working now. You can look up a company and see who you know who works there, or who works there who knows people you know. Believe me, you already know people who you should connect with on LinkedIn. You never know who could help you get a job someday.
As you are being interviewed, and shortly after you start, people will talk about the reason for hiring you in oddly specific terms, as though the whole reason for hiring you is to work on one particular six-week project.
You will feel instinctively competitive with the other new hire. Ignore this.
Everyone will seem busy, but basically pleasant, and they will stick you somewhere with a chair, a desk, and a computer and a pile of documentation to read. They will probably schedule whiteboard sessions with you to explain the existing system, so take notes.
Give yourself the gift of the big picture; resist (somewhat) the urge to notch up some specific deliverables right off the bat.
Among other things, no later than your first week, you should look closely at an org chart. In particular, you must know the name of the CEO, the person who runs the company. This person will affect your life, and people around you will talk about this person frequently, often using only their first name. These days, most org charts are online, so there is a photo. Know what the CEO looks like as well.
While you're at it, you should also know the name of every person in the chain from you up to the CEO as well. You may think, I'm just here to code, I'm not into the bureaucracy above me, let the suits play their suit games. Fine. I'm not saying obsess over it, but just take a little time to have some sense of the larger context.
Befriend QA, get someone of them to show you how to set up/configure the system from scratch. Take notes, but don't panic if you miss some of the details. These people will inevitably forget that you don't have their context and will dive right into details. You will feel stupid wanting to pop up seven levels and ask the big basic questions. Ask them anyway, but at the right times.
Your boss: your immediate manager, manages 5-15 people. Contrary to the stereotypes in TV shows and movies, this person is rarely a jerk. Actually your friend. Busy, harried. Hard to pin them down and make them talk to you.
That said, being a manager is stressful, and different people handle that stress in different ways. Managers compete with each other for resources and visibility. Some managers manage down, that is, they see their main job as enabling you, their workers, shielding them from political nonsense, and helping them do their work as well and happily as possible. Others manage up, that is, they see their main job as playing the political game with their peers and directors, and thinking about you and your problems is an afterthought.
One of the disorienting things about starting a new job is that you don't land on your first day in a stable environment, in which there is a set way things are done, and an agreed-upon set of goals. The place is always in flux, cutting over to the new system/way of doing things. Your boss will quit/be laid off/transfer away three weeks after making the decision to hire you. There will be a reorg that most people will bitch about and say makes no sense. Your company will acquire some other company and no one will know what that means. Even your hiring may be part of some empire-building on someone's part that doesn't work out, or perhaps be seen as such by some people.
Every company is terrible at internal communication.
There is always someone who says "This place is the most fucked up place I've ever worked." In exactly those words. Every job.
So - you are installed in your cubicle in the engineering department of a company. What is that like? First, there are other parts of the company, and at first you will only have a dim idea of what they are and what they do. You should probably try to put a little more energy into figuring these things out than you will ever feel like.
First, there is the person at the top, the CEO. Usually, this person did not rise up through the ranks of engineering to get to be CEO. Usually they came up through sales. This is not necessarily a problem, but sometimes they can be pretty disconnected from the actual technology the company makes and sells, and they sometimes undervalue the amount of work engineering does, and engineering gets ignored. If so, this will definitely affect your life, even if you yourself don't even know the CEO's name.
Business school fantasy: marketing does all kinds of research, goes out into the world, keeps their ear to the ground, talks to customers and potential customers, figures out what they all want most badly that would be the least amount of effort to do, then tells engineering what to create for the next release. In practice, they provide almost no useful guidance about product direction. The most successful projects tend to be under-the-radar skunkworks projects within engineering itself.
Sales: You won't have much contact with them. They fly around a lot and have expense accounts. The problem is that they love to say yes to customers, and don't themselves have a good idea of the scope of what they are saying yes to. They love to sell promises as if they were reality, and then it suddenly becomes engineering's problem to make it happen immediately. It seems like they consider it boring to sell what you actually already have sitting on the shelf.
There is a particular arc I've seen in a couple of companies I've worked at. Maybe it's just coincidence, and you will never run into it, but it goes like this.
The company starts out with a small collection of cocky, talented engineers, and they come up with a great product that launches the company to initial success. Shortly after, the suits that run the show fire the VP of engineering, beloved and respected by the staff, and hire someone else. They liked the initial success, but want someone who can "take the engineering organization to the next level", and they are anxious to bring "maturity", "stability", "professionalism", and "consistency" to the organization. They hire an old-school authoritarian who knows or cares nothing for engineering, your technology, your products, who buys your stuff and why. He just cracks the whip. He holds meetings with his immediate underlings, publicly shames those that don't adhere to the milestones he imposed, and has a strongly hierarchical sense of corporate organization. Little people understand technology, he's just there to make the trains run on time. After two years, after all the day-one senior people have left for greener pastures, the execs realize their mistake and fire the jerk VP of engineering, but by then you are in a death spiral, hopelessly behind the curve.
Periods of lateral drift are only recognized in retrospect. We felt terrifically busy at the time! Sometimes you only realize after the fact that wow! We haven't really released any substantial new features or products for two and a half years!
Before I get into the actual practice of coding itself, there are certain challenges that face engineering as a whole that might not be obvious from just taking a bunch of courses about programming.
One thing you don't really get in your gut until you have seen it up close and personal is the huge gulf between a proof-of-concept prototype on one hand and a product on the other. As programmers, we tend to think yay! I wrote the algorithm and it works! I'm pretty much done, the rest is details! Wrong. There are tons of issues, and complicated issues, around making something releasable and supportable and configurable and usable.
How do you bundle your in-house written software together with whatever third-party software you use in such a way that everything looks to the customer like a seamless product, but where everything is appropriately versioned, and such that later on, you can have a perfectly accurate grasp of what the customer is actually running? So-called release engineering is tricky and demanding and easy to screw up.
In addition to knowing (and being able to reproduce later) exactly the versions of all software that go into a given release, you better have a clear idea of this information for your tools as well: compilers being the biggie, but all "background" stuff. With regard to tools and third-party software, there is a danger that you stick with some snapshot in time, and never move forward. This is terrible and it happens all the time. It always seems risky to change without a compelling reason (if it ain't broke, don't fix it) but you end up shipping software that is six years out of date and long since no longer supported.
How do you take a whole bunch of projects of different scopes and durations being done by different groups, and come up with a release plan and schedule that will be palatable to your customers? Obviously you only want code that has been rigorously tested to get into an external release.
Some people think that each release should have a few large tent-pole features, and whatever minor features and fixes you can fit in. Other people point out that with that scheme, you often end up pushing out the release date because one of your tent-pole features isn't done yet, and more stuff keeps getting poured into the release, and it becomes unmanageable. As an alternative, you can dictate that there shall be a release every three months, and whatever fits, fits, and if some project isn't ready in this quarter's release, then it won't go in until next quarter.
The problem with this is that frequent releases often annoy customers. How do you feel when Microsoft wants to upgrade Word out from under you? Nervous, right? I was happy with the old one, a new version could only screw me up. Engineers, however, hate that customers are out there running ancient code. Once you've released something, you want everyone to upgrade to it. Supporting old code is a royal pain.
Source control presents a whole raft of ways to shoot yourself in the foot as an engineering department. Because I went to college back in the dark ages, I never heard of source control until I got my first job. Perhaps they cover it more now, but my guess is that they don't dwell on it much.
When you have a massive amount of code in the form of many, many files spread across many directories, with many people editing, changing, and adding to that code, you must have source control. The idea is that each engineer has their own environment on their computer, and they are free to edit, test, and break code to their heart's content. When they think their code is ready, they "check it in", that is, they commit it to the public codebase, and all the other engineers will pick it up. The closer the final deadline gets, the more conservative you will be encouraged to be regarding what you check in.
Generally, if you break the build, you are publicly shamed. There absolutely should be nightly automated tests that run on the current state of the public codebase, and these absolutely should pass every night. If they do not, someone checked something in the previous day that broke the build.
OK, so the problem with source control is that you can split the code base into "branches", so all the people working on one project with one deadline can check into that branch, without any interference with another group working on another project with a different deadline. Eventually, of course, all branches must come home and be reconciled on the main trunk. In theory. Sometimes, every single engineer gets his or her own branch, although not often. This proliferation of branches, and splintering of the code base can be a nightmare of epic, unrecoverable proportions. Sometimes there are so many branches, whose common ancestor is so far back, that there is simply no way to ever bring them back together again. This is an unmanageable situation. Beware places that go branch-happy.
There are certain files that you specifically do not want to branch, that you want there to be only one version of across all branches of all code within your company. This is often not handled well.
In addition to source control, every company you will program for will have a bug tracking database. Sometimes enhancements are tracked in the same system, along with actual bugs. You will spend your whole life at any company with at least some open bugs logged against you at any time. They are assigned a priority in the system, and some indication of desired fix-by date (either a calendar date or a particular future release). Now you can push back on bugs, closing them as "not a bug", "not reproducible", "duplicate of another bug", or even some variation of "nah, we're just not going to bother fixing that". Usually, the bug tracking database is hooked up to the source control system, so that if you check in code related to a particular bug fix, the code check-in is referenced for all time in the bug entry as well. That way, years later, you can look at an old bug and quickly see exactly what lines of code were changed or added to fix it.
In addition to the danger of going branch-happy in your source control, there is a related danger of going OEM-happy. OEM stands for Original Equipment Manufacturer, and it means your company has a relationship with some other, usually bigger company whereby the other company will take your product, slap their label on it, and sell it as if it were their own. Sounds great, right? You get the benefit of the bigger company's army of salespeople and their name recognition and distribution networks, whatever they are, etc. Almost no engineering work, since what they want to sell is almost exactly what you are already shipping.
OK, so they might not want a whole lot of coding changes, but all the overhead associated with engineering except the actual writing of code must still be done, and it is not easy. This new product may share 99% of the code with your existing product, but it still wants its own codebase, with some formal, controlled way of fixes and enhancements flowing from the OEM codebase to your regular codebase.
It is not just automatic anymore. At the very least, the other company gets its own source control branch, or rather, its whole own trunk. Their release schedule, I promise you, will not match your original desired release schedule. It will have to be supported as a whole new entity. And so on and so on. The moral of the story is that the actual engineering turns out to be a small part of engineering, and that OEM deals are seductive to the people in suits who make such deals, because they look easy - the costs are not obvious, but they are still big.
Even if you don't have explicit OEM deals, you will, hopefully, have customers. Some will be rich enough and important enough to you to push you around, in that they ask for special features, or custom, proprietary modifications. They may throw lots of money at you in return, or promise lots of sales if you just make these changes. In all my years of doing this, I (as you can tell) have lots to say about the industry as a whole, but I have one single slogan I keep coming back to. You ready? Here it is:
The road to Hell is paved with specials for rich customers.
The quick little features always turn out to be more complicated than you thought, and they share some of the problems of OEM deals. The money and/or sales somehow often don't ever quite materialize. Worse, they exert a subtle pull on the minds of engineers and managers. You get focused on making the rich customer happy with their weird little interface or bell or whistle that no one else wants, and you take your eye of the ball of what everyone else actually does want. You have limited resources. You can't afford to not do what most people out there want, because that is what your competition is doing. This is called opportunity cost, and it can be a hidden cost, only visible in retrospect.
One of the most surprisingly hard things to do is version numbering. It's devilishly hard to get right. Generally, you have a product release called, say, 4.1.26, which means major release 4, minor release 1, build 26, or patch 26. So far, so good. As you issue new patches for security updates or bug fixes, you will come out with 4.1.27, 4.1.28, or you will eat up some numbers with internal-only builds, and skip ahead to 4.1.43. Then you come out with a few new substantial features, and you issue 4.2.4. Then, every couple of years you come out with a new major number release, like 5.0.2.
But here's one problem. Engineering has all these automated tools to increment build numbers, and they rigorously control this stuff, but marketing really has ultimate authority to decide what to call the product, including versioning. I have seen situations where some customer paid for a release, say 4.1.26, and the way the contract was written, they don't want to pay for any other release just yet, but they have a horrible, crashing bug that they demand get fixed right now.
OK, we're engineers, we fix bugs for a living. We fix the bug, using the 4.1.26 codebase (which was released from engineering some time ago). We find and fix the bug, and check in the fix to our source control system and remake the code, bundle it up, and deliver it to the customer as 4.1.27. "No," they say. "We want it to be 4.1.26, but with the fix." But you can't have two different versions of code out there in the wild, one with a horrible bug in it and one with the bug fixed, that have the same version number! That's exactly the kind of situation version numbering is supposed to prevent! That's the whole point of version numbering! So say the techno-nerds. Who do you think is going to win that argument, the nerds or the customer waving the wad of money, and their best buds, marketing?
When your codebase branches, some versions that get released can end up with features that are lacking in branches with later release numbers.
You want to be able to give your customers your product with a single unambiguous version number, but there are layers of versions. Your product will often consist of lots of third-party code, and your in-house written code will be only one component of it, with its own version number. Sometimes there will even be different in-house written components, each with its own release number and release schedule, in addition to whatever third-party code you bundle. You want there to be a clear distinction between the whole product version and the version or versions of your code. This can easily be confused.
The biggest difference between what they teach you about programming in school and the practice of programming for a company is that in school, each assignment starts with you and a blank screen. Write a brand new program to do X. As soon as your program accomplishes X, yay! You're done! Off to the next project.
This almost never happens in real life. You never start with a blank screen, and you absolutely must be at least as concerned about what happens to your code after you "finish" as you are about getting it to work in the first place.
I call myself a programmer, but the truth is that if I wore one of those GoPro cameras around all day, only about 10% of my total time would be spent actually typing new code. So really, I am much more of a code reader than I am a code writer.
When you are hired, there might be 100 person-years of existing code, and a product whose first release was six years ago, and which has gone through several major releases and innumerable patch releases since then. It is impossible to teach in school how a large codebase ages over time, after many people, with varying degrees of competence, have made changes upon changes upon changes. There is a world of difference between how you think when you are doing large-scale software development as part of a multi-year product, and how you think when you are writing one-off programming assignments.
Before you touch this code to make a change of any kind, you are going to have to understand a whole lot of code that other people have already written. So I spend most of my time reading old code, not writing new code. For this reason, if you want to make me twice as productive a worker, don't make me twice as good at writing code. Make me twice as good at reading code. The only way for this to be the case is if everyone else has written code that is easy to read.
If you've been asleep up to now, and you fall asleep in a minute, if there is only one thing you remember from this presentation, it is this: please, please, please write clean, readable code. School teaches us to write clever, even brilliant code. Efficient code. Compact code. Within reason, none of this is anywhere near as important as writing clear code.
I once read that the top priorities when writing code are:
In that order.
I would even switch the order of the first two. I would rather work with code that was clear but didn't work than code that worked but wasn't clear. And if you aren't writing clear code along the way to making it work, you will find it much harder to get it to work in the first place. I also wonder about the last item being on the list at all. There is a famous quote that premature optimization is the root of all evil. Phrased slightly differently, I once read this about optimization:
So this is all those CS 101 platitudes that sound like scolding, blah blah blah. But they matter! You can hear and intellectually understand all that stuff about how to write "good" code, and pay it lip service, but you don't understand in your gut how your life depends on it until someone comes screaming into your cubicle saying that a huge customer has just hit an embarrassing show-stopping bug, and they need it fixed now now now, and you are the person to do it. You identify the code in question and it is a huge, steaming pile of spaghetti. Not only do you have to fix the bug, but you must do it minimally - you better not break anything in the process! You must tweak the code just to fix the bug, but it is hard to figure out what the code is even trying to do. As you delve deeper into it, you end up very often saying, "How did this ever work?" You convince yourself that it couldn't ever have worked, yet it did. Then finally you find that five obvious bugs all coincidentally canceled each other out, and this chunk of code over here is never even called by anyone, so maybe it somehow managed to work most of the time. But you can't fix all that stuff! Just fix the bug. Then beg your boss for time to go in and rewrite the whole mess in the next release.
Looking at old code is like looking under the wall of an old house. You will spend a lot of time reverse engineering old code that ultimately is way more complicated than it should be. Fix it along the way. Code written by you six months ago might as well have been written by someone else, as far as your ability to understand it is concerned.
You will spend a lot of time tracking down bugs. It is almost always the case that you could get away with making a single change, and "the bug", as reported, would disappear, but it is also almost always the case that by the time a bug manifested itself in externally observable bad behavior, things went wrong at about five different levels. Be aggressively curious, and fix all these. Usually fixing the root cause of the bug will be the last change you make to the code, after you have fixed all the problems at other levels.
Just naming things right is one obvious way you can make your code readable, not just to other people, but even yourself 18 months from now. Naming [variables, routines] accurately and precisely takes just a bit more effort than 95% of people are willing to spend. It is hugely important. Naming things well is an extremely powerful way to help guide or remind you what code is doing and how it is doing it. Naming things in ways that actually mislead is horrible. You might think, I'm smart, yeah, I know what that routine really does, I can cope with the fact that its name lies. No, you can't. Having to correct internally, in your fallible human brain, for misnamed stuff in code is a needless tax on your cognitive machinery. Fix it. Global search and replace is your friend. If things change over time and an old name is no longer correct, take the three seconds to think of a better, accurate name, and switch all the references to the new name.
And by the way, when I was in school, they placed a lot of emphasis on lots and lots of comments. Too many comments can be just as bad as not enough comments. Let the code speak for itself where it is obvious, and only comment when you actually have something to say that is not clear. But think before you type, and provide enough context that some reader down the road will understand it. Cryptic comments are as bad as cryptic code.
All those buzzwords that sound like spinach you are being forced to eat are true and good: modularity, information hiding, walls between functionalities. Make it difficult or impossible for a chunk of code to know things or play with things it has no business knowing about or playing with. Separate clearly and cleanly, what you are doing from how you are doing it. Too often people fail to do this, and the overall logic of what they are trying to do is interthreaded with a lot of arcane detail as they walk through an array of objects that contain pointers to elements in another array, etc. etc. and after an hour and a half of reading this code you realize that all they were trying to do was some dumb little thing, and half the complexity there is never used, and maybe never was, or maybe stopped being used three releases ago.
You can get away with being dirtier the more the dirt is confined behind a clean API. Don't reinvent the wheel. You can't know everything, but we engineers tend to underestimate the extent to which the problem we face today requires a whole system to fix. We tend to want to do it on the fly, and as the way we are solving our problems gets more and more complex, we are reluctant to step back and separate and formalize the way we came up with to solve a problem and cleanly separate it from the problem itself. If you have a problem and it can in any way be seen as generic, google! Look for ways other people have seen this problem before. Use standard libraries, look for textbook ways of solving your problems. Resist "roll your own".
One way you can accomplish this is by devising a generic engine, or library of utilities, and render the so-called business logic of what you are trying to do as a table. The engine just walks the table and does what is specified there. Once the engine is bug-free, all the intelligence is in the table. For bonus points, externalize the table as a whole separate file, so you don't have to recompile when you change the business logic.
Copy-and-paste code is the devil's playground. You write some code, and it is good. you need the same or similar functionality elsewhere, so you copy the code over there. Lather, rinse, repeat. After a while, there are six copies of the same chunk of code. You have made someone's job (possibly your own) six times as hard as it should be. Moreover, what if one of those six copies is slightly different? That makes me tear my hair out. Did someone find a bug and fix it in only one place because they didn't know about the others and didn't bother to look, or is this difference only actually pertinent to this particular use of this replicated chunk of code? It takes ten times the effort to answer that question as it would have to have simply avoided the whole issue in the first place.
There is no such thing as useless but harmless code. Once a chunk of code has been superseded by other, better code, rip it out! The codebase is not ever supposed to be a museum of yesteryear's half-baked ideas about dreams that never came true, or old ways we had of doing things before we had more requirements dumped on our heads. If you ever need to know how something was done in the past, that's what source control systems are for. In general, engineers err on the side of leaving things in instead of ripping them out rather than the other way around.
Whatever the job, whatever the fancy new thing your company is trying to do, there is some basic text, either an actual textbook, or some international specification, that lays out the basics of the area your company plays in. Read this. Read it again. If you are like me, you may find it hard to do at your desk, because there are always a million things, like answering email, that you can find to distract yourself. So print it out and go to a coffee shop, order a latte, and settle in for an hour and a half and pound through it. It is amazing how far ahead that puts you. You would think that everyone in the whole office must have practically memorized that thing as a basic entrance requirement, and you'd be wrong. Actually reading, and understanding, the ur-text is tremendously clarifying, and puts you way ahead.
Similarly, for whatever language you primarily code in, find a good book about that language, and read it cover to cover. Really understand it, and occasionally dip back into it to refresh yourself. You will be surprised at how many people aren't familiar with even some of the basic features of their main language. Even if you don't often use certain features, you should have some idea that they exist and what they are for. That way, when you do need them, you will at least know to look them up instead of fumbling forward without them or worse, achieving the same effect by hand.
Larger companies tend to be more bureaucratic and process-bound. Don't believe the lie of write specs first, lock them down before you write a line of code, then write to the specs without revision. The theoretical fantasy is this. You write the functional spec along with the development test plan. Logically, the test plan falls out from the functional spec. Then write the design spec, and the actual writing of the code is almost an afterthought, about 15% of the total time/effort. Then go through the development test plan, and when you have quantifiably passed all the tests (or logged official bug reports), hand off to QA and support them.
In reality, no functionality exists in isolation from incumbent code, and no functionality exists independent of design. So-called "prototyping" goes on as part of the design process. This is otherwise known as writing the code while you write the spec. The devil is in the details. Moreover, as you get further into the code, you will come across corner cases and issues that reflect back on the overall functionality and you will think of other things to test for. Therefore all three of these documents (functional spec, development test plan, design spec) are euphemistically called "living documents", which is a tacit acknowledgment of the fact that you will keep changing them and adding to them up to the final day of the project. Any management that does not accommodate this either implicitly or explicitly is dysfunctional.
It is awful to work in an environment where you have to break the rules and sneak around to do a good job.
On the other hand, in a lot of places, if you ask what specs you should write as part of your project, they'll look at you blankly and say "specs?"
My own personal habit is to write specs as I go, even if no one asks me to, and even if only I ever read them. Sometimes forcing yourself to articulate, clearly and concisely, what you are trying to do and how you are trying to do it can help you a lot at the time, and believe me, a year or so out, it is worth its weight in gold to have such a document. For extra credit have one clear, short section toward the beginning of the document titled "What Problem Are We Trying To Solve?"
I already preached to you, and I cannot emphasize enough, the virtues of readability and comprehensibility of the code you write. That fundamental principle guides a tremendous amount of my thinking with regard to what constitutes good code as opposed to bad code. The other core principle I hold, that also guides a lot of my thinking, is this. You are the weak link, not the computer. Program to make humans' lives easier, not the computer's.
You are slow, distractable, error prone, and expensive. The computer is a trillion times better at the things it does well than you are. But you, the programmer, are that indispensable creative spark that makes everything happen. As much as possible, offload as much of the onus of finding bugs onto the computer. Make it more likely that the computer will find your bugs in any and every way you can. As much as possible, make it likely that if you do something you didn't quite intend, that the computer, either at compile time or run time, will yell and scream. It is not a good thing when the computer is a permissive hippie, "It's all good, dude". This is something I don't like about python, for instance. Compared to C or C++, it's a permissive hippie language.
I love asserts. Only idiots don't. The worst bugs of all are those that only show up once in 109 packets (or whatever). Be aggressive in your use of in-code sanity checks, and make the penalty for failure severe (assert). When you add a feature or fix a bug, if practical, make sure a test for it is added to the automated nightly sanity tests. Code rots. Make sure that if your new feature or bug fix ever gets undone, someone will be forced to notice within one day.
Similarly, I have come, over the years, to love strong typing. Use the "const" keyword.
There is an old but true saying about how the expense of fixing a bug
increases by a factor of 10 at each step:
developer unit test → QA → field trial → release
Let me tell you about an incident that happened to me. I was sitting in my cubicle, doing whatever it is I do, and my coworker came in and asked if he could bounce some design ideas off me about what he was working on. "Jeez," I thought. "I don't even know his area of the code or his project at all. What can I contribute?" He started explaining his design to me, drawing boxes and arrows on my whiteboard, and I gamely tried to comment, half faking it, but he actually thought about what I said, and drew more boxes and arrows, becoming quite animated. After a while, he was satisfied that he had a good design, and left, saying, "Thanks for being my Ken doll". He explained that sometimes, to get your thoughts clear, you need to describe them in plain English to someone, even if they don't actually contribute anything back. In similar fashion, in one of my anthologies about programming, there was an essay in which the author said that he had a manager once who kept a teddy bear in his office. If your design was ready, he would have you explain it, in simple terms, to the teddy bear. If you couldn't, you had to go back and rework the design. He found this to be an immensely helpful and clarifying exercise. Don't hesitate to be someone's Ken doll, and don't be shy about using someone else as your Ken doll!
Finally, throughout my whole career, both as a fresh-out-of-college kid and as a grizzled veteran, I have been repeatedly surprised and humbled by the people who stop what they are doing and help me when I have asked them for help. Notice when this happens to you and pay it forward. Remember the patience and support others have shown you any time you are in a rush and someone, perhaps from an entirely different department, comes to you and awkwardly asks how to solve some problem they have, even if you aren't sure how to solve it yourself.
Any of Joel Spolsky's books. He has been a techie programmer, a manager at Microsoft, and an entrepreneur, so he knows the industry from all angles. He thinks and writes very clearly and entertainingly as well. He was most active writing in the 1990's and 2000's, but all of his general observations are spot-on. He also occasionally writes on his blog.
One of Spolsky's projects was a tremendously useful site called Stack Overflow. It is a huge forum where programmers ask other programmers technical questions. If you google some obscure question about how best to do something in python, the top search result will likely be someone asking and answering your very question on Stack Overflow.
"The Mythical Man-Month" by Frederick Brooks. THE classic book about programming, how to do it and manage it right, and how to do it and manage it wrong. If you think that no book related to computers written in the 1970s could possibly have anything relevant to say now, you're wrong.
Funny 7.5 minute video about an engineer in a meeting. Yeah, sometimes it feels like that.