Drinking from the Fire Hose |
So you're fresh out of school with a shiny new CS or Software Engineering degree, congratulations! You are totally unprepared for the reality of being a software engineer! Seriously, you are about to be thrown into a pit of despair and suffer imposter syndrome! You're going to have all of your algorithms at the tip your tongue, your data structures and graph theory raring to go and... find out that almost all of it is rarely used because it's all implemented in libraries for the languages you use. Which isn't to say that it was useless because you still need to know what you're looking for. But so disappointing.
This piece is not even comprehensive so in fact it's much worse than what I paint here. And if you're in some specialized field you'll have to learn that specialization on top of all of this, like if you're doing scientific or robotic programming. But all is not lost: despite this long, long list of things you're going to have to deal with the vast majority of engineers make it, and even excel in it because they're so excited after the grind of college to actually start using the knowledge for their craft.
Development
Development Environments
Let's start with the physical development environment. Pre-pandemic, the standard issue space for engineers is to put them on banquet table in row crops pretty much shoulder to shoulder. The rationale was to "promote interaction and communication", but the reality is that the first thing people do is get headphones and turn them on high so they don't hear anything and are impossible to be distracted. So the rationale never passed the sniff test which lays bare why HR does this: money. The pandemic has fortunately shown that the shoulder to shoulder physical "networking" that never happened in real life is trivially replaced with a longer wire which was the way it actually happened. Chatting across town is just the same as chatting two seats down. This is a win for everybody, but you will have to learn how to get into routines when you telecommute and how to deal with human interaction when it's needed. Fortunately, just about everybody is in the same boat figuring this out so you actually have an advantage of not having any expectations.
Next up, they have probably had you write some code while you were in school. You may already be writing your own code for your own projects. Maybe they specified the development environment maybe they let you choose your own. If the former, they likely threw you into some fancy integrated development environment (IDE) which are often language or platform specific (think Eclipse for Java or Xcode for Apple). IDE's can be nice, but they aren't inevitable and they can too often become their own ends, rather than a means to an end (see section on Frameworks). Learning them and their idiosyncrasies can be time consuming. My experience is that they are often rife with bugs (looking at you Xcode) and inexplicable behavior where googling is your only hope. And there are lots of them making the likelihood of fuckery exponential.
While you're not always going to have a choice because of operating environments or company mandates, it's really good to have a base line capability to edit code, compile or whatever you need to run it, and be able to debug it where debugging by printf is perfect valid and good. There are tons of editors out there and its religious which one is best, but vi, emacs, and others are all good choices. Emacs is, of course, the best because my god told me so and I believe Him.
Debugging
The ability to intelligently debug is an essential capability of any software engineer. They probably didn't teach you how to debug other than mentioning that debuggers exist for whatever languages they were using, and then more likely not even that. There are a variety of ways to debug something from simple using of printf and tailing logs to sophisticated debuggers with breakpoints, watchpoints, the ability to look up variable symbolically with arbitrary expressions, etc. For things like the web, there are built in application specific debuggers that make traversing the DOM really easy.
Debugging is much akin to the Scientific Method. First of all you find out that something is misbehaving so you make a hypothesis about what might be going on. You then create experiments to test your hypothesis and rinse and repeat until you have a hypothesis that meets the observations. You can then attempt to fix the problem which further confirms your hypothesis. Code review is really nothing less than peer review in the Scientific Method where outsiders can throw darts when the fix looks like a Rube Goldberg contraption that look like it fundamentally misses what the core of the problem is, and that what you have is a bandaide not a fix.
You will learn about one of the deadliest of all bugs: the Heisenbug. Like the Heisenberg Uncertainty Principle which states that you can't know a particle's position and momentum at the same time with accuracy, a Heisenbug similarly vanishes when an attempt to observe it is made. It is maddening and especially prone with multi-threaded code with race conditions.
A cousin of the Heisenbug is the Schrodingbug. While hunting for an obscure and intermittent bug you finally find the cause and how to fix this. Unfortunate for you, you cause the bug's wave function to collapse and cat died and all hell breaks loose until the patch is applied. Once you detect that this code should have never worked at all, it's curtains for the kitty.
Learning Languages
It really annoys me and is a peeve that companies hire for $LANGUAGE programmers. The reality is that languages are tools and frankly the differences are mostly angels on a pinhead. You will learn languages over your life (see SHINY below) and they will change and evolve whether they need to or not (see FOR ITS OWN SAKE below).
It's not to say that language differences are superficial, but a lot of them are. By far more important in my opinion is the richness and consistency of the libraries that you can access easily from them. Some languages get traction with new stuff going on and become go-to. A current example is with machine learning and Python. I haven't checked for sure, but I doubt there is anything inherent with Python that makes it good for ML, it probably just sort of happened. If you have a not-run-of-the-mill problem, libraries and other goodies should be a big consideration in choosing a language.
Last there are some important considerations for language choice. Memory management with garbage collection or not. Object Oriented or not. Raw access to hardware features or not. Typed or not. They all have their tradeoffs and as always there ain't no such thing as a free lunch. My experience is that most tasks don't require much resources so it far more important to optimize for the speed of coding and maintainability -- not the speed of the code. Even if you end up having a scary inner loop, you can often architect it to drop into low level code which is controlled by a higher level language, either as a native extension, or some other means.
API's and Libraries
API's and libraries are the lifeblood of writing code. When you're learning a new language a lot of your time is going to be spent hunting down how to not reinvent the wheel. No seriously, you don't need to reinvent malloc (though I have). You need to get the skills to find things so that you can concentrate on whatever the problem at hand is. When you're new to the scene you're probably going to be given a language to work with which will imprint on you like a baby bird. Mama bird is goodness and never wrong. There can only be one best mama bird and her ways will always be the one true way. Those of us who have been around a long time see mama bird -- and the whole flock behind her who look for all intents and purposes the same.
That's not to say that all API's are of similar quality of course. The C runtime library is sort of a mess and definitely shows its age, and why things like PHP and PERL who slavishly copied it really missed their chance. But once you get standardized facilities like oh, say, hashes it really doesn't make a lot of difference if they are called a Dictionary in Python or an Object in Javascript: they all do pretty much do the same thing albeit with different interfaces and/or syntax. Your job is to recognize these patterns and then go hunt for the equivalent in whatever the base API's are for your language.
API's can also be used define calls over the net for various services. In this case you are not required to make the API represent a Remote Procedure Call (RPC) and frankly those seem to have gone out of fashion (buh-bye SOAP), but it is conceptually the same as pushing parameters onto a runtime stack to call a method. With both, you'll learn that what is needed is protocol agreement. That is, the thing making the call and the thing interpreting the call must agree to what parameters are there, what is optional, and so on.
Network based API calls may or may not come with language specific wrappers that hide some of the messiness. You should probably take some time to see what's going on under the hood at least for a few of them just so it's not so mysterious because some day you might be called on to to design one yourself like it the next section.
Writing Libraries and/or API's
Hopefully this is not one of your first tasks because designing libraries and API's is really an art as much as a piece of technical competence. There are a lot of variables and considerations to designing them. Designing them upfront is usually the easy part because you have some functionality that you want to expose and that is purpose built for a given task. Great! That was easy. You most assuredly fucked up.
Requirements change. Features are added. You get more and more users using your API. How easy is it to modify the API? Can you make breaking changes? Can you deprecate things that were fuck ups? How do you design API's that are more resistant to breaking changes? How do you go about deprecating mistakes and/or obsolete? How much churn with users is acceptable?
I'm not going to say what makes a great API designer because I'm not an expert at it and when I've had the chance I like to beg, borrow or steal from existing API's. But the one piece of advice I'd give is to be very reluctant to expose a net facing API for as long as possible and make certain that it makes sense from a business standpoint. Maintaining code that is used by thousands of sites that started out as a "gee it would be fun for my programmer friends to be able to play with this" will make you very sorry you didn't think it through.
Algorithms
The truth of the matter is that the vast majority of programming is mundane. For any one project, there is likely to be only one or two interesting algorithms. If you're at a place with more senior engineers, that algorithm is not going to have your name on it. I've been lucky to have been able to design some really interesting algorithms at a young age, but I was working at startups, one of which I was literally the only software engineer. Sometimes the sharks aren't hungry when you're thrown into that shark tank, but these days VC money is usually not naive on that front and if they are, they view it as a lottery ticket which will get re-engineered if needed.
What you can do is find those key algorithms and find out why they are key. Was it implemented well? What are its advantages? What are its deficiencies? Has it been optimized? Does it really need to be optimized to make an operational difference? If you think it could be improved and it will make a positive difference should you bring it up with who maintains it? These sort of things are usually somebody's baby and you're about to call it ugly. You have to learn to be tactful. If you can prove your changes in reality rather than theory that bolsters your case.
"Hey, I've been trying to understand $ALGORITHM and have been playing with it on my own. Here are some things I hacked on and made it $X percent faster is this reasonable or am I missing something?"
Frameworks
Ok, I'll be right up front: I am a framework skeptic. See my section on "for it's own sake" for one of the big reasons. Like designing a computer language or writing an operating system, it is often the life goal for every self-appointed hot shot engineer to design a framework to do something. That every other framework that has come before it is shit and Only I Can Save You. Sorry, you're not and the chances that your framework is anything beyond mediocre are vanishingly small. Unless your entire existence is wrapped up in your framework and its evangelizing, your chances are pretty much zero of it being important.
As for frameworks themselves they are far too often Procrustean. The author has a view of the world and the only way to salvation is to view it that way too. Rails advertises itself as shamelessly having that attitude, but the fact of the matter is that they all have that attitude even if it's not stated. Frameworks get old and creaky, often a victim of their success without acknowledging their shortcomings. New frameworks are a dime a dozen and usually riddled with bugs and poor design and in the end don't solve the problem any better than what they are trying to replace.
To keep whipping Rails, people went oohh---awww when a single command could generate a web site with all of the CRUD operations generated from templates connected to database tables. Nobody had the presence of mind to ask who would use such a web site. In the early 80's Ingress was a relational database which had a front end program called QBF (Query by Form). Rails is essentially QBF 40 years removed. And thus without asking those basic questions an entire generation of programmers started using Rails, all to find that that is not how real web sites are designed.
The flip side of frameworks are its users and for young engineers, leads us to the next section...
Shiny
Shiny is a subspecies of Fear of Missing Out (FOMO). Young engineers are completely convinced that most senior engineers are complete idiots who are stuck in their ways and that if only they had youth and vigor they would be able to appreciate the sheer beauty and worth of $SHINY. Of course it's going to revolutionize everything. I mean, they say so themselves! I'm reminded about AWS Lambda when it first came out. What is this I asked? Investigate a little: oh, new age batch jobs. Oh and Docker, what is this? Investigate a little: oh, new age time sharing. For the most part nothing is new under the sun and it's all been done before. The canonical trap that young engineers spring with Shiny is lock in. Somebody isn't giving you this wondrous new miracle for the good of humanity. They are far too often trying to lock you into their walled garden. Shiny is almost always the enemy and should always be viewed with extreme suspicion. We didn't get these grey hairs for nothing.
A subsection of Shiny is language-isms. Lots of languages like to generate new and shiny ways to code something up that is completely idiosyncratic to that particular language and are impenetrable to somebody not as familiar with the language, or even people who are very familiar but are not caught up in the desire for Shiny. If you can design something with relatively language independent constructs and it doesn't materially hurt performance goals, it is far preferable to do that from a maintenance standpoint. Not all engineers have the same level of language archana and even if they don't need to fix something, they might need to look under the hood at how it works for maybe a similar problem. Don't be that dick who makes it impenetrable gratuitously.
For Its Own Sake
All things fill to available capacity. It's the law of the land. Something similar happens with software projects: they don't know when they are done. The can't know when they are done, because that admits there is actually an end state which is tantamount to defeat with all of the similar projects who can't know they are done for the same reason.
Like Shiny, newer should not be taken as better on its face. If something is working well for your purposes and has good bug and security patching, there isn't a lot of motivation for upgrading for the sake of upgrading. Upgrades cause churn and either create bugs or expose bugs. The latter is OK, but the former is not worth it unless there is good motivation.
Interacting with the OS
At this point the world looks pretty Unix-y. I don't know what the percentages are for servers in the backend but Linux has to be dominate. For front end you basically have three choices: Web, IOS (Unix), and Android (Linux). Windows as an OS is not terribly relevant since writing native apps for it is pretty stagnant. Yes, laptops and desktops are still overwhelming Windows, but that doesn't mean it has a lot of relevance to you as a new programmer. Linux and its distos are generally free and you are free to pick and chose. Windows is a business model with walled gardens they are enticing you to go into. It's best to stay out. Same goes for other walled gardens like AWS.
So you'll need to learn the basics of how Unix OS calls work. Much of that is abstracted away in higher level languages where you can open a URL as easily as you can open a local file, but a lot of the API's in the OS parts of languages are patterned after Unix OS calls and Berkeley sockets. You don't need to go crazy understanding every system call -- mmap and brk are probably not going to be thrown at you any time soon -- but open/close/read/write/lseek may. Unless you're writing relatively low level, you're probably not going to need to know much about how signals work, but if they gave you a intro hardware architecture course, they are hardware interrupts translated into user space.
Mostly you need to get familiar with the basic of the OS itself but just as importantly you need to get familiar with all of the utilities from the command line. It's OK to not know how to use find(1) off the top of your head because man(1) is there to help you. Using locate(1) to find files, learning how to redirect output so you can show somebody else that something hosed is going on... all of these things are going to be a daily part of your job. You're going to have to learn them quickly because they didn't teach you any of this in school.
Networking in Reality
They probably taught you about the OSI network stack. Unless you're a networking geek like me that's probably about the amount you need to understand the plumbing that goes on after you bits leave your program. But there really is much more to it than that within your app. The world is pretty much clients and servers with clients doing CRUD'ful operations and servers serving it up. You need to also learn about things like Websockets which unlike the client server paradigm, servers push out data proactively to clients. Think chat clients. You'll also need to get an understanding how to distribute load for server pushes so that you'll end up need to understand message buses like RabbitMQ.
If you are doing anything remotely web related you have to learn about Ajax calls (aka HttpXmlRequest). The are a fundamental part of creating a modern interactive web app. My personal favorite design pattern is to have a skinny backend which has two purposes: serving up data, and performing access control. For the front end, it takes the raw data and builds the UI. Others may like more in the backend, but fundamentally it's going to be a mix at the very least. The days of static web sites are long gone.
If you want to add audio and video and especially conferencing you're going to learn how all of that is done. You don't necessarily need to know the nitty gritty of RTP transporting the output of codecs, but it is useful to know that you have those tools at your disposal if they are appropriate for an app. But you might need to know that integrating a point to point conference is cheap and easy, but if you need audio mixers and video muxes it becomes a more costly endeavor.
Bottom line is that the world is a network and it's the way you deliver data from source to destination.
Optimization
There are 10 types of people in the world: those who have heard Knuth's quote about optimization, and those who haven't. Knuth's quote is:
"The real problem is that programmers have spent far too much time worrying about efficiency in the wrong places and at the wrong times; premature optimization is the root of all evil (or at least most of it) in programming."
If you don't even know who Donald Knuth is, your degree is revoked. While it's good to not write gratuitously gross algorithms, the truth is that most code does not need to scale to anything significant. Your main job just is to just get something up and running, not create a master's thesis. O(N) is just fine for the vast majority of in-memory searches.
My general philosophy is to get something up and running and figure out where the hot spots are later. This is especially relevant with the unending changing requirements and scope creep. The thing you were assigned in the first place may end up looking nothing like what it ultimately needs to do in the end. Any optimization you do is very likely to be wasted time. Instead of spending a lot of time optimizing, spend more time making your code easier to refactor. That is, always consider hedging your bets that code might need to change in ways that reflect new requirements. One way to do this is write pluggable architectures. Don't go crazy though. Only do it for the highest level stuff.
For core mechanisms for key algorithms some amount of optimization is fine mainly to prove that it can be optimized further if needed. Optimization follows an exponential curve with efficiency on X and effort on Y. Sometimes companies will pay millions of dollars to optimize algorithms which are asymptotically close to zero like Fintech trading, but they are exceedingly rare.
Performance Profiling
So you finally were forced to face needing to optimize that you assiduously avoided by reading the previous section and postponing optimizing. Optimizing is something of a science and an art. It's both getting concrete data about what is going on and then often re-imagining how the same requirements can be done in better ways.
The are really two different kinds of profiling: ad hoc where you are pretty sure you know what's running hot, and then using built in profiling software provided by the disto or the language you're using. The former is easier to grok because you know what you're looking for and are trying to confirm or deny that it is part of the problem. The big problem with its is that you can be wrong in your conclusion about the problem and instead just nibble at the edges.
Bring in performance profilers. Profilers usually work by setting up hardware clock interrupt and sampling what's going to produce a histogram of where the program has been and how much time it's spending. The classic for C programs is gprof. Various higher level languages have bindings for their own profilers too which is necessary since things like gprof doesn't understand, say, Python's internal symbol table or anything like that. They are no panacea. Often they can be misleading because they show core functions getting hit, but not who is calling them. Some may have the ability to record farther up the call stack but usually at first blush it's just the active call that is recorded. And then of course profilers are invasive so if you have any kind of real time quality to your code, you get Heisenprof to contend with too. Last for upper level languages, their profilers can be fiddly and not terribly well supported. They are often idiosyncratic in my experience and not all that easy to understand.
Fortunately profilers are not needed or even useful for a major source of poor performance: database interactions. The curse of trying to map an object model onto a relational database is the source all kinds of gigantic fails. Remember that "wow, I pressed enter on 'rails new foo' and I now have a web app"? Uh, yeah. Rails is hardly the only perp here, but it is very representative of the mindset of not thinking about what is happening in the underlying database. Out of sight, out of mind. Except when it matters.
It is essential that you understand how the underlying database works, and how to see if the ORM (Object-Relational Mapping) is making good decisions about handing the mapping of objects to database. This is true of *any* database type, not just SQL. By way of example to show that even I who has been around relational databases for 4 decades, I can get snookered too. The simple way with Rails is to create models which are really just a layer on top of database tables and let the ORM deal with the joins. I had a small table with the definitions of a particular items which I'd join with another table which would fetch those definitions. This saves space in the other table since it's just endlessly repeated data.
The problem is that the other table got really, really big and it was indexed using a B-tree. B-trees you know full well with your shiny new degree are O(log n). So having to look up a key in the big table may involve looking up many many intermediate nodes on the tree at disk speeds (even with flash, it's still bad). I might be getting exactly how the problem manifested wrong because it's been many years, but the jist of it is that joins are not always your friend. Sometimes data replication is far and away the better choice.
I'll end here by saying that you should always be suspicious of tools that make things "easy" as you scale up. You should also have some humility to say that you have no clue what is causing something to be slow and that it will take some time to investigate it. Last, know when to say good is good enough. It's extremely tempting to once you've opened the hood that you tinker with it for a month, weeks longer than it really need to be tinkered with. Stop while you're ahead.
Databases
Databases are the beating heart of almost all applications. As shown above, they can also make or break an app when used incorrectly or carelessly. While nobody is saying that you should be a full blown DBA who knows every nook and cranny about SQL archana, it is good to understand the basics of what they do and why they do it.
There has been a somewhat recent backlash to SQL like in the last decade with NOSQL databases. This is by and large SHINY in my opinion. In the vast majority of cases you do not need Cassandra and its ilk, and using it is pretty much showing that you have no clue and are just following trends. Likewise things like MongoDB. If you don't understand ACID and its tradeoffs, you are doomed to repeat every mistake that comes from being ignorant of it. Databases are really simple and fast when you don't support the hard things that slow them down.
Here's the thing though: it's not like relational database vendors are blind and can't see the good parts of current trends. Mongo pioneered using JSON blobs and querying based on that. Postgres saw that and went "great idea, we can do that too!". I don't even know at this point if Mongo can be ACID compliant (I imagine it is now), but they had to retrofit it back in while things like Postgres has had it for decades and knows how to optimize when the various aspects of ACID are not needed.
The other thing you need to learn is that databases are pretty much forever. Once you make the decision to use one, you are going to be saddled with it on a project pretty much for life. Consider the problem you'd face: trying to go from databases that even if they are both SQL they have different extensions which are incompatible. Multiply this by fail if one or more are different kinds of database. Now consider that you are making this change while you're whole system is running in a world that doesn't sleep.
Lock-in is multiplied by a zillion when it's a database on a hardware walled garden like AWS. Not only are you locked into a database for life now you're locked into a hardware platform too. You are fucked squared. Don't do that.
Your biggest take away should be that you are not a database expert just because you have a new CS degree and that generations of people studying this vital problem are not idiots. There are places for specialized databases where scaling is extreme but the likelihood you have or will have that problem is almost zero. Be extremely conservative.
Threading and Concurrency with Cores
Like a lot of things parallelization is as much of an art as it is a skill. When I was young I found out that I had a somewhat uncanny ability to visualize concurrency and especially race conditions that the company's engineers we were contracting for were amazed. I don't really know if it's ingrained or not, but it is something you'll need to at least be aware of if you're using threads and other parallel programming.
They no doubt taught you about mutual exclusion, but it's trickier to figure out whether it's needed or not. Mutexes are definitely not free so you want to avoid using them if at all possible, but if you miss one that is needed, prepare to weep because it will probably arrive as a Heisenbug. So you're going to need to be able visualize the situations when one thread can mash on common data causing another thread to puke.
One last thing since I don't want to belabor this much is that there is an unfortunate misconception that throwing cores at the problem with multiple threads will solve everything. If you are using an interpreted language think again: you need to understand the Global Interpreter Lock (GIL). With almost all higher level languages, there is lots of code that is not reentrant and thus not thread safe. Languages get around this by locking the interpreter when it needs to execute that code. The net effect is that all of those nice and fancy cores you are paying more for by the minute cannot be used except in cases where a thread blocks. There are quick diminishing returns on hardware cores in the face of a GIL. There are some languages which don't have a GIL but they are far and few between. The answer for the most part is to just have a bunch of processes with different interpreter instances. Sorry threading, you are no panacea.
Top Down/Bottom Up/Middle Out
Now we come to development styles. Different organizations or even dev groups are going to have different styles. Some times that will be out of necessity be strictly enforced. You don't want your space telescope launched to a La Grange Point to have coders be haphazardly playing around and fix bugs as needed. I personally have never understood Bottom Up but there are people who are like that including a friend of mine for whom I found it pretty maddening.
Top down is also necessary in situations when you outsource writing code. If you get into a situation where some suit has the bright idea that they can save money by outsourcing writing code, the first thing you learn is that outsourcers will write exactly what it is you asked for and nothing more. They won't hedge bets, build in future proofing, prepare for obvious new features. Nothing. They write to the spec and that is that. If you are bad at writing specs, you're fucked and it's your fault. If you have changes in requirements as always happens or that the there are ambiguities in requirements you pay. If they are significant you may pay a lot. I personally would not want to do this, but there may be a time when you have to. The key take away is that they are a business trying to get your money.
My personal favorite way to develop is middle-out. I often have an idea that sounds interesting but I'm not sure exactly how it will play out. I don't have all of the requirements and am not entirely sure what it is that I'm going for. So I build quick prototypes and see what happens not paying much attention to code cleanliness or speed or anything else, just trying to understand the problem space. This lends itself well to rapid prototyping, especially if you need to do a sell job to management to see where your head is at and why what you're working on is useful. If it's not evident, I'm obviously a fan of the 20% own time kind thing that Google and others have.
Refactoring
Middle-out programming explicitly relies on refactoring as a strategy: you find out that something is worthwhile and then you refactor it to clean it up and make it real. Refactoring is the process of looking at all of the moving parts and see how they interact with each other. You often find that parts are reusable in ways that you weren't thinking of when you originally designed a piece of code. This isn't a failing and if there is a failing to be had, it is on the side of over-generalizing things that are not in fact general. In my opinion, the more private methods you have, the better. Methods should only be promoted to public when there is a clear need because public implies support. If you have a public method, others have the right to bitch if it doesn't work correctly for their use.
Refactoring is just a way of life with writing code. You'll be doing it often and it is inevitable. But there is good refactoring and bad refactoring. Good refactoring is like washing your car to get rid of the gunk that accumulates over time. Bad refactoring is like finally getting around to fixing your car after three wheels have fallen off. Be the former and treat your car well.
Compartmentalization
Everybody is taught about modularity but in reality is is a skill you have to learn on the fly. Like most things, there is a happy medium. Lots of young programmers make a zillion and 7 modules or classes that have one of two methods and that's it. Since they end up in separate files most often, it makes it a pain to search for them. Many methods are really purpose built support methods for a public method. The likelihood of their reuse is minimal and can be detrimental if you have to hunt down who is using that method if you want to change its functionality.
In my opinion methods should be private until proven otherwise. DRY (don't repeat yourself) is nice in principle but it can be it can be taken to extremes where instead of a nice purpose built helper function you have this rusty Swiss army knife with blood all over it from people trying to use it. If a method has a shitload of mode and flag qualifiers, it's probably that knife you'd sooner avoid.
On top of that, short little functions/methods likely make it harder for things like CLANG and GCC to do loop unrolling. Maybe they can do this across modules/classes but they most likely have to be more careful. I don't know how much loop unrolling various interpreted languages do, but it's probably even harder. The main point here is not to go crazy with DRY as unifying principle.
Tools
Building tools is an often overlooked essential skill. Lots of programming is repetitive where you need to monitor something or generate output to be munched on to get stats. Basically anything and everything. When I was a young engineer I was one software engineer in one company supporting dozens of engineers in another company with a hardware product (a laser printer) they had contracted us to build. They had no experience with embedded systems and were getting a crash course on how you write for and debug them on the fly. This was before email across sites and I lived a hour away from them so didn't go down and visit often.
I had the idea that I needed a debugger for the hardware for my own purposes. I decided to make it pretty fancy so that I could view variables symbolically, set break points, do profiling, etc. I thought that this was pretty neat from a feather in cap standpoint, but frankly in hindsight it is probably the single most important thing I did that caused the project to succeed. The reason is that it gave our client's engineers a familiar looking way to run and debug their code in something that was otherwise totally alien.
The moral of the story is don't underestimate how hugely important tools can be. It's easy to get wrapped up with tools as their own ends, but most companies are tool-poor not tool-rich.
Googling and Other Scrounging
So you freshly have your degree and can spout every algorithm and data structure with pinpoint accuracy to the recruiters in the hiring process. You land you job and proceed to use that knowledge to write your version of all of those algorithms. They then fire you. Why did that happen? Because wheel reinvention is a waste of time, and almost certainly your implementation is going to suck in comparison to somebody else's who probably wrote a masters thesis on it decades ago. So why do they ask you those questions in interviews? Because you are green and they are lazy.
The reality is Google-fu and being able to scrounge on the net is the way that actual research is done these days. Stackoverflow is not just a handy site on the net for programmers it is the expected way you'll find answers to your questions. You have a bizarre error message that makes no sense? Google it explicitly and see who else was stumped. Google-fu is its own skill and you need to learn it. Figuring out the right incantation can be a dark art in many cases, but the more you work on it, the better you'll get.
UI Design
You are not a UI designer, you say. That is for somebody else and never shall your estimable engineering hands be soiled with such dirty inconsequential details. You are in for a rude surprise. Nobody says you have to be an ace graphic designer with taste right out of Italian fashion houses, but everything has input that needs to be ground on and then shipped out. You may not need to do the actual window dressing itself, but you may need to make a first approximation that is so hideous that actual UI designers can't wait to get rid of your affront to the design sensibilities.
Frankly everybody should know the basics HTML layout and some CSS. Even if that's not your day job you often come in contact with the need for internal tools. Far too often internal IT is not going to fund one of their folks to do this work and then -- more importantly -- maintain it. And your UI designers aren't wasting their limited time making your tool look better than the turd it is. So it's going to be up to you, and you're going to have to learn this on the fly too.
Security as Actually Practiced
Interacting with the World
Network Security Basics
OS Security Basics
Web Security
Logging In
Role Based Access
Permission Based Access
Figuring Out Security Requirements
Testing
Testing Along the Way
Unit Testing
Integration Testing
Regression Testing
Interacting with DevTest
Working with Teams
The Mythical Man Month
Interacting with Others
Source Control
Requirement Gathering
Bug Management
Feature Management
Constantly Changing Requirements
Meetings
White Boarding
The Interrupt Stack
Development Process
Different Process Types
Waterfall, Agile