If the students are being graded on a curve so they're competing against one another, this would be downright diabolical.
If the students are being graded on a curve so they're competing against one another, this would be downright diabolical.
In the past few years, I've worked repeatedly with existing code bases to extend or modify functionality (or just fix problems), and it has been an amazing learning experience. Not all of the code has been good, much of it has been questionable. But the process of understanding someone else's code, figuring out how to change it, balancing the harm of forking vs the benefit of improving, dealing with pull requests and merges… there are a number of skills you will only learn from years in this kind of environment.
One huge challenge here is when you don't work in an organization that understands software. People aren't knowledgeable about software are often impressed with green field programmers "jim wrote the whole thing". Truth is, understanding a different code base and getting to the point where you can improve it and contribute back to it is (at least in my experience) far more challenging than just writing something from scratch. It seems that most people who write software are already well aware of this, but it can be a hard sell outside (in fact, you may even find that people seriously question your progress - a green field app there's something nifty and new to demo even day, with existing code bases, there's a lot of confusion and investigation - and it can be very dispiriting to spend days stubbornly trying to understand and diagnose a feature, finally solve it, and realize that the team is disappointed with your progress).
However, the good software teams appear to be well aware of the challenges, and it's an essential learning experience.
For example, the conventional wisdom that functions should be less than a page of text and that they should do one thing and as abstracted as possible actually makes it quite hard to understand what a program does when you aren't familiar with it.
Instead, I'd propose first writing straight line code, and then abstracting out the common functionality into separate functions as necessary. Casey Muratori (the guy behind Handmade Hero) calls this compression oriented programming [1].
It does? Maybe you mean something more extreme when you say "as abstracted as possible" but for /reading code/, I generally find it easier to get the high-level picture and avoid getting bogged down in minute from code that looks like this:
function sendReport(params) {
var dataSource = findDataSource(params);
var data = dataSource.generateData();
var report = reportBuilder.build(params);
sendReportEmail(report, param.recipients, this.template);
}
... versus a flatter one-page implementation with 15 lines of data loading, 20 lines of report, 5 lines of email templating and 15 lines of communicating with the mail server... all sprinkled with variously scoped try..catch blocks. It also helps me focus in on the part of the task I'm really interested in without trying to load the entire system in my head.For /writing code/, I do agree the reverse is often best: start by doing the simplest thing that works, and then refactor it into something cleaner.
function do_task() {
do_step1();
do_step2();
do_step3();
}
function do_step {
step1();
}
function do_step2 {
step2();
}
function do_step3 {
step3();
}
But I've rarely seen that be the problem. It's been far, far more common to see giant functions that are much more understandable when broken up.Usually I am only interested in specific parts at any given time, either because I only have a particular task to accomplish (e.g. update the email template) or because trying to understand everyone at once would overload my little monkey brain.
If I'm trying to just update the email template then I don't care about the part the loads the data and I'd be wasting my time reading it. Sometimes you can't even tell if an chunk of inline code is relevant without spending a decent bit of energy figuring out what it's doing. Having everything in a single function also makes it easier for lines of unrelated code to become entangled together and thus harder to understand.
Well-structured code gives me a choice; I dive in and see the details if I'm interested, or I can leave it for another day and focus my attention elsewhere.
Certainly though, good tools like IDE are vitally helpful in reducing the friction in peeling back the abstractions when necessary. The nature of some languages though means that the level of tool support available can vary widely, which probably has an effect on different prefered coding styles. In Java, for instance, it's pretty easy to find all the pieces of code that call a particular method, while in Python, it's often not really possible without running the code.
Also, I probably should have been clearer above. Sometimes a short function is the right approach, particularly for something that is going to be done over and over again, but the overall goal is clarity, not short functions.
Anyways, it's not worth getting too hung up on I don't think, it seems my opinion is the minority one.
I've never seen this discussion play out where the "short function" side is saying that the only goal of short functions is...short functions.
Obviously "clarity" vs "short-functions" is a no-brainer discussion. But fails to acknowledge the complexities and nuances being discussed.
Shouldn't that 10 line function be pretty much self documenting?
Done right this should really help get a good grasp of code base quickly.
Now, if the function has bunch of side effects and you HAVE to read it to make sense of what's going on one abstraction higher, then it is done wrong.
This hasn't been my experience at all. Functions as small as 100 lines can absolutely destroy my comprehension to the point where I need to refactor it into smaller functions to be able to understand it. I'm dealing with one such function (at 200 lines) right now at work.
Well, let me rephrase. I can see what it's doing easily enough. I just have no idea what it's supposed to be doing, on a chunk by chunk basis, because it's all hand rolled search loops and array manipulation.
I also know the final result is incorrect.
I'm sure I'll slap my forehead and wonder why I couldn't see the problem before after I'm done refactoring it.
I, however, do it differently. I start from a high-level of abstraction, and work my way down. At the top level I might have a function called "ImportData". I write that call down, then move to the next line where I write "RunDataAnalysis". At that point, if I decide to move down and implement the second function, I forget about what ImportData does, how it does it, or whether or not I've implemented it.
All I know is that the data structures are populated with the "Imported" data, and proceed to code along with that assumption. Obviously if I test, it won't work, but that's for unit-tests. You do unit-tests, right? Can't do that meaningfully with giant functions, but I digress.
To me, functions are a form of "interface". It delineates responsibility and expectations between different bits of code. I'm not a chaotic and/or artistic programmer and I'm definitely not a genius wunder-programmer that can hold the entire functioning of the program I'm writing in my head at any time. What I can do is systematically break-down a problem into component pieces, negotiate their interaction, and make it solve a problem.