Like you said, it's been a complete game changer. I feel these skills continue to differentiate me from my peers in terms of how I can attack arbitrary problems bravely to this day.
This could be causal.
What I find is that if my code isn't working, I stop what I'm doing. I look at it. I think really hard, I add some print statements and asserts to verify some assumptions, and I iterate a small handful of times to find my faulty assumption and fix it. Many, many times during the 'think hard' and look at the code part, I can fix the bug without any iterations.
This almost always works if I really understand what I'm doing and I'm being thoughtful.
Sometimes though, I don't know what the hell is going on and I'm in deep waters. In those cases I might use a debugger, but I often feel like I've failed. I almost never use them. When I helped undergrads with debuggers it often felt like their time would be more productively spent reasoning about their code instead of watching it.
Are you aware of the amazing Java debugger feature of "Drop to Frame"? Combined with "hot-injection" (compile new code, then inject into current debug'd JVM), it is crazy and amazing. (I love C#, but the hot-injection feature is much worse than Java -- more than 50% of the time, C# compiler rejects my hot-injection, but about 80% of the time, JVM accepts my hot-injection.) When working on source code where it is very difficult to acquire data for the algorithm, having the ability to inspect in a debugger, make minor changes the the algorithm, then re-compile, inject new class/method defs, the drop to frame, the re-exec the same code in the same debug session is incredibly powerful.
The plus side of less capable tooling is it tends to limit how complex software can be--the pain is just too noticeable. I haven't liked java in the past because it seems very difficult without the tooling and I never had to do enough java to learn that stuff. Java's tooling does seem quite excellent once it is mastered.
If you are a low level programmer, I understand your sentiment. A piece of advice, when you need to use Java (or other JVM languages), just submit to all the bloat -- use an IDE, like IntelliJ, that needs 4GB+ of RAM. The increase in programmer productivity is a wild ride coming from embedded and kernel programming. (The same can be said for C#.)
When you're extremely "fluent" in programming code, understanding exactly what the code does by looking at it, stepping through it doesn't typically add all that much.
While I do use a debugger sometimes, I'll more often form a hypothesis by just looking at the code, and test it with a print statement. Using a debugger is much too slow.
This varies, but in a lot of environments, using a debugger is much _faster_ than adding a print statement and recompiling (then removing the print statement). Especially when you're trying to look at a complex object/structure/etc where you can't easily print everything.
Don't remove the print statement. Leave it in, printing conditionally on the log level you set.
Early on it was a godsend. Start program, hit breakpoint, look at values, step a few lines, see values, make conclusions.
Now I rely on print statements. Most of all though, I just don't write code that requires stepping. If it panics it tells me where and looking at it will remind me I forgot some obvious thing. If it gives the wrong answer I place some print statements or asserts to verify assumptions.
Over time I've also created less and less state in my programs. I don't have a zillion variables anymore, intricately dependent on each other. Less spaghetti, more just a bunch of straight tubes or an assembly line.
I think it's possible that over the years I hit problems that couldn't easily be stepped. They got so complicated that even stepping the code didn't help much, it would take ages to really understand. So later programs got simpler, somehow.
Neither situation was at all related to my talent (or lack thereof).
Besides, every IDE has a different way to debug, so they might just not be familiar with the interface. I can’t tell you exactly how to debug in VSCode even though I’ve used it the most. I’ve had to run a debugger only a handful of times in the past couple of years and it’s always for codebases that are more tangled (e.g. .NET where there’s interfaces everywhere).
Unless you have a setup where you can easily run one system with a debugger attached while connecting it to everything else, you’re basically restricted to running a debugger for bugs that can be reproduced locally.
Now maybe if I was better that wouldn't be the case, but even the cleanest "wish i thought of that" functional code i've seen still looks like it'd be easier to fail fast using a debugger with.
In fairness though, I will admit I use the debugger a lot less when i'm not screwing with reflection on generic types or whatever because runtime errors just happen a lot less in functional styles. Usually if it compiles, it runs, because the compiler can sanity check the code better than you can.
> [uses F#]
That has to be humblebragging. The average .net developer is terrified of or doesn't even know about F#.
The only reason F# winds up feeling harder isn't so much how F# works, but simply because the entire dotnet environment was built for C# first, so you do need to know how to handle the C# style. I do think that if you could just pick one and then suddenly have every library support it's styles, F# would probably be easier overall because it's just got a lot of nice features built in that make updating your code so much easier.
C# is not a simple language, but F# has basically all the complexity of C# with OCaml on top.
`f(a, b)` looks like a call to a function that takes a single tuple as its argument. So I would expect `f` to have the type signature `A * B -> C` instead of `A -> B -> C`. Is my intuition wrong? If it is, then what does F# use the parenthetical syntax for?
[1] https://learn.microsoft.com/en-us/dotnet/fsharp/language-ref...
F# enforcing a lot of good practice code style stuff (order matters for example pisses off long time devs who already have styles, but prevents SOOOO much stupid bs from beginners) and basically eliminates runtime/chasing variable state errors completely so long as you can stay within style. Yes it'd be nicer if there was only one way to invoke functions but if i had to take a tradeoff I think it's a pretty easy one.
It is an issue that yes, like your example, you're often stuck ALSO learning OO because "oh you want to use X library, well that's OO so...", and even then you can isolate your mutable/OO areas really well, but this is more of an issue with it being an second fiddle language. If F# got F# specific libraries for all the C# stuff out there tomorrow I think it'd take off and most people would never look back.
If we're talking basic business logic/beginner programmer stuff, yeah I think F# offers a lot of stuff that makes it flat out easier to use. And if you want to point out complex issues, I feel the biggest one is that something that's intuitively much easier to understand in OO (create a variable/object, populate it on each iteration of a loop depending on logic), can feel daunting as hell in F# (fold).
You're noticing a very real relationship.
It's easy for people with functional programming knowledge, or who had OCaml as their first programming experience like I did at university, but for people without those exposures I can understand the difficulty.
Oh? Did you win some award? I'm curious as to how you're determining this?
One major issue I do see coming from the C# side is "well how do I do this then?!", which often the answer is "you don't, because you don't need to" or "well what if it's more performant to do it mutably!" well then thankfully F# can absolutely do that.
If you keep an open mind it's really a very clean and simple language, but in an age where half of development is importing 8 well known libraries, not being the main supported language is a major weakness.
However, I wrote my first thing a port of a small C# tool of a couple hundred lines, to F# in about an hour. It's been about a week now, and things are considerably smoothing out.
To be considered niche, the F# tooling has been great. Also, having the .NET libraries available adds a lot of built-in capability.
F# isn't hard, it's just different. Hell in many ways i'd argue it's much much easier once you get used to it. It doesn't have the support C# does so often you're stuck with a library that WILL work but doesn't have documentation for doing it in F#, and that can lead to struggles, but that's not really a sign of being a good coder.
Most dotnet developers could probably code circles around me in F# if they knew it existed/gave it a chance.
Personally I stuck with it because it had the low code look of python with strong typing. It took a bit to wrap my head around some functional stuff (basically map/iter = foreach and if you want to update something on each loop you probably want a fold, or more likely a built in function), but once I got over that hurdle it was pretty smooth sailing.
The irony is that by far the hardest part is the library thing, which your average dotnet dev would handle WAAAAY better than me.
I’ve been programming in C for nearly 20 years and primarily used printf for debugging for the first 12-15 years, and have used debuggers more and more. I use Emacs, and its gud mode is so nicely integrated into everything that using gdb is truly much, much faster than the alternative. I don’t use print debugging at all anymore.
It takes less time and cognitive overhead to just stop the program on the same line you would have inserted your printf, but you can now inspect the entire program state.
Obviously I’m not saying anything revelatory if you use an IDE on a regular basis… I guess this comment is for the folks who eschew IDEs.
Logging alone is fine, but it can often be difficult not to drown in information. I do embedded programming in C mostly.
I work on compilers and debuggers are more of an hindrance than help when trying to fix a compiler bug.
I believe there is a similar situation for distributed systems.
I used to read every help file on windows / visual c++, the FreeBSD manual, plowed through the file system and tested this it. Just because I was curious
I have seen devs scoff at the thought of print debugging, but I recall that in systems programming there are many times you can’t use a debugger or need to rely on other tool.
I’d rather schools teach the concept of step debugging vs runtime debugging. Teach students to try to understand the code, make hypotheses, and verify them.
I have seen some people use a debugger solely because they only know how to step debug. Meaning they start from main or another entry point and step through every line of code.
My point being that, you can’t judge a developer by if they use print statements or a debugger. Judge them by the methodology of how they debug.
Next time they do, ask them to recommend a better method of debugging that works across generally all languages, compilers, IDEs, and platforms with next to zero configuration.
Taking a program trace from the print debug statements, grepping through it repeatedly to filter down to certain events of interest, looking at the interleaving of those events, and figuring out the order that things happened in to cause it to go off the rails. That sort of thing. (To be fair, time-travel debuggers can start to get at this, but those are pretty uncommon. Traces, as you say, work almost everywhere.)
Or better yet, compare the traces between working and non-working runs to see how they differ. I've looked at diffs of traces this way before. (Sometimes I'll first use a small script to renumber pointers in traces by order of appearance.)
I've also used this sort of strategy before for debugging rare threading or other non-deterministic issues. Have the shell run the program in a loop, saving each run's trace and results to a different file, go off and get lunch, come back and see if anything failed. Then look to see if any of the runs failed and look for the structural differences between the working and non-working traces.
I can't imagine sitting and stepping through in a debugger 100+ times in the hopes that maybe this time, it will be the run that's just different enough to trigger the bug and that the debugger itself won't prevent the issue from manifesting. Not to mention, trying to remember the steps from all the good runs and spotting where the bad run goes bad before you've stepped to far. No thank you.
I think people really underestimate print debugging. Debuggers are fast and easy for simple bugs, sure, but there's powerful stuff that you can only really do with printed traces.
Isn’t that where things like breakpoint conditions, data breakpoints (“break whenever X value/field changes”), and dependent breakpoints, work pretty dang well? You just set up the appropriate situation and let it run until it breaks
But one thing that I have found helpful in the past has been aggregating the information from the traces across runs. Comparing good with good and bad with bad to classify the commonalities, and then compare good with bad to see how they differ.
The debuggers in most popular IDEs (IntelliJ IDEA/any JetBrains IDE, Visual Studio, VSCode, Eclipse) work the same way. You set breakpoints (typically by clicking somewhere around the line number), step into and out of functions, and look at the memory state. If you learned how debugging works in IDE X, you can easily switch to IDE Y without having to learn much.
Not just systems programming, but fixing issues in scaled production systems as well. Have fun attaching a debugger to the process that got killed twenty minutes ago when the spot instance it was running on got reclaimed. If you don't collect telemetry, you're blind. Debuggers are a luxury that you get to enjoy for issues you have before you ship.
I looked over her shoulder as my wife was doing a CS degree, and I realised there's a bunch of these little things that make life a lot easier if you know them.
um yea thats my job dude and i fought for it so other people should too these people waltzing in not knowing shot should not get into CS
But they also knew the precise formula for college entrance, and were laser focused on getting into an "elite" school. Any activity that didn't contribute to that process was eschewed.
It's not like how it was when I was growing up. We spent $2000 on a Gateway PC that I had full admin access to, and unfiltered internet. Now that kind of money is being spent on cellphones in the household. Because I had that kind of access I was bricking the OS (and reinstalling it), Mailbombing people's AOL inboxes, putting Sub7 on Kaza and Limewire and remotely messing with peoples computers.
The computers at school were only networked my senior year. Even then they were basically wide open.
Me and my friends effectively operated a screwdriver shop out of whoever's house we happened to be at. We were trying to host game servers on whatever we could cobble together.
None of our families had cellphones because they were still only used by businessmen. Computing priorities were way different and how you were exposed to them was different as a result.
None of those opportunities are anywhere near as common now. Kids get devices in schools but they're basically bricks that log everything you do. Computer Labs are rare and their access is fully locked down. Most students first personal computing device is a cellphone, and more likely than not it's an iPhone. All phones are super locked down but apple's even more so.
The market has changed. The days of open computing are now relegated to the back of the line or for those who can afford it.
So it's no wonder a class like this exists. We see similar issues in middle school where students don't understand what folders are or where their files are stored. Phones and tablets make it so easy you never have to think about it. This also extends to collage as well.
Okay then dont.
This is more or less unique among college majors outside of some arts disciplines like music. Yes, there's a requirement for some secondary school algebra and some basic science but an electrical engineering major could basically have never assembled a circuit before attending college and probably wouldn't be at any particular disadvantage.
And, per the original post, MIT is certainly one of the institutions that does this. The 6.001 MOOC(s)--basically intro to algorithms--teaches a bit of Python on the side but clearly you're intended to mostly learn it on your own.
(By contrast, back in the day, I took a FORTRAN course as part of a non-CS engineering major. The assumption was that you had never touched a computer before.)
I see it more as a trade. There can certainly be some beauty to it. I’m sure coal miners hold some of their own in special regard too.
Meanwhile I agree with you, software engineering jobs are a trade 90% of the time. It’s why so many highly educated folks are being laid-off right now, while the doers keep-on building and making bank.
It’s spelled Doerr
Computer science is a pathway for those that want to delve deeper, and that necessitates rigorous fundamentals in applied mathematics and information theory.
I dare say that one can go through a whole career as a software engineer without spending a single semester learning about algorithms, in the sense that you just start learning about them in a JIT fashion if/when they become an issue, which is rarely.
Some of the O(X) trivia so much sought after in some interviewing circles is even fundamentally wrong when real world aspects such as problem size or CPU architecture come into play.
When I started doing job interviews it was a little bewildering how much some companies focused on this. I ended up taking a job whose interviews focused on more practical skills and also stressed a good culture fit and understanding of development practices. It feels like a place I'll be happier.
I think you're right about the JIT aspect to this sort of thing--that's really what being an engineer is about in my experience. When you encounter a problem, figure out how to do it better. If my filter/sort/whatever is too slow for a specific application, I'll do some research and implement something more appropriate.
I can't complain, since I was introduced to Emacs along the way, which I still use heavily to this day. And while I don't really use Scheme anymore, it made Elisp trivial to figure out.
When I hear people starting their CS degree with C++ or Java, it makes me cringe.
Your point about engineering is exactly right. I built a working radio in my first term, having never done anything like it before, save for acing some physics exams with minimal electricity sections. When we came to coding, I had mucked about a bit with a computer, but I also found other students who'd worked at Microsoft. Wide wide range, and a lot of people of course ended up getting that fellow to email them the solution.
It seems completely reasonable to expect someone to be comfortable finding their way around a computer if they’ve chosen to major in computer science.
I’d also expect an entering electrical engineering major has studied more math than just “some secondary school algebra”. They’ve probably already studied some calculus or at the very least are ready to as soon as they start their first semester.
It’s a bit of a red flag to have no foundations in a subject you’ve decided to focus on for the next four years. That said, sufficiently motivated individuals can catch up and overcome their initial lack of preparation.
At that time in your life your entire world has been school, and everything you did in school led to another thing in school. Why wouldn't you assume you'd get taught programming when you applied for a CS course, whose prereq was math that you did in school?
Doesn't mean you'll be a great programmer or archtect or planner or teacher. Right? you might drop it and go into something else.
Same is actually true of a management degree, everything they teach has been well thought out and is applicable to managing a company. Doesn't mean you'll be any good at it or work in all those departments. It is not a useless degree, though many useless people might pursue it.
This was my "back in the day" experience as well, though I had a choice between C and Pascal. There was no expectation that anyone had any programming experience. I only had a touch of C64 BASIC knowledge going in.
> I have this feeling that the kids who appear better at uni are the ones who happened to pick up certain not-quite-programming skills before they started. Basics of networking, how installation of programs happens, how to use the command line, that kind of thing.
This one really stood out to me and highlights a change in things since then. That describes ALL of the CS kids during my time. We were the ones who a) had computers and b) did weird things like rebuilding kernals or futz around forever trying to get remote X sessions running.
A fact that blew my mind a few years ago was that, of the 13 software engineers on my team, only 3-4 actually owned a computer other than the work-provided one.
One of the later assignments they gave us this "bomb" executable and we had to use gdb to pick it apart and modify the instructions or find ROP gadgets or something to make the code not "explode". He was my partner in the assignment and I spent most of the time trying to teach him what I was doing in gdb. And trying to express that, sincerely, he wasn't stupid GDB is just really hard and he didn't have the background knowledge to make learning it easier.
I mean, yes, this was me. I came in knowing most/all of this. I knew six (or more?) programming languages, had at least played with CVS/SVN, had installed Linux (read: fought with the Linux bootloader to get my AMD CPU to boot without crashing), had dipped my toe in a few open source communities.
But I was a tutor in college and I interacted with a bunch of people who didn't come in with any of this experience. Many of those people struggled, but I also know a bunch of people who came into CS knowing nothing, loved it, and went from zero-to-sixty faster than even the people who came in knowing a lot.
I'm still not sure I can identify what the ingredient was, but experience alone is not enough to explain it.
https://www.youtube.com/watch?v=ZQnyApKysg4
It's all about leveraging "simple" cli tools and combine their powers to cut through work very swiftly. The opposite of many days for many people.
If only I could use 529 funds on some of these online course providers.
What this content covers should unlock iteration speed, which is the single greatest lever in learning and growing faster (on a computer). Thus it gives you more cycles to go back to improving your code, experimenting with algos, etc. Probably also highly correlated with upwards mobility in the software job market.
Great seeing this under a common umbrella I can hand to students and new grads.