I studied Software Engineering at Swinburne University in Melbourne Australia, and we did exactly that.
One subject was "Software Maintenance Project" where we took an existing code-base we didn't write (a front-end for laTex for our year, the CVS code base for the next year students) and continually made changes to it. Halfway through the course we had to deal with changing requirements. We were graded on the quality of our code and how well it fit the existing style etc., not how fast we did it.
We also did a full-year final year project with 16 of us where we created a BIG code base from scratch. We decided to use an iterative approach (our choice) and so over the year we ran 6 iterations continually building on what we'd already done.
We also did awesome courses like "Personal Software Process" to teach us discipline in how we personally approach writing code.
EDIT: Sometimes I post to HN "hire/resume" threads about my hire-ability and how I personally think I'm "better" than the average person who studied CS because of things like the above. Normally everyone disagrees and says as long as I can code, it doesn't matter that I studied Software Engineering and am a certified Engineer. It's for reasons like the above I still personally feel I'm stronger than the average CS grad.
My one piece of advice: keep studying and practicing making beautiful software, in and out. Be careful who you take technical advice from; the industry right now is flush with cash, and thus there is a huge incentive to talk more than produce. Projects are often killed as quickly as they're started, causing people not to practice program design.
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.
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.
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.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.
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.
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.
It was the first time I realized that bugs in earlier assignments don't magically disappear, and changed my thinking dramatically.
Setting a reasonable pace with the project could be tricky, but I think it would be very beneficial.
Internet time does not justify poor program design.
Saying: "Use the same thing you worked on last quarter" dooms everyone who made wrong foundational choices with their old project to spend most of their time "fighting the last war".
Not unless you told them before they started the previous year's project that it'll be used further along the line.
I'd just make it one really long subject that spans 3/4 years.
You had the option of building them all completely separately, but you were strongly encouraged to re-use code form earlier revisions, particular for the client-server bits. The class also included labs on Subversion, although nothing on the notion of things like code review. I could imagine teaching the course today with Github and requiring (or at least awarding some credit for) good use of Pull Requests, meaningful code review, etc.
Sadly it looks like the course no longer exists; I have no idea what replaced it.
What you don't learn in school is how to write maintainable code. This is a much different style of writing code, and much more valuable once you go out in the "Real World". I'm not sure if there are CS programs out that start with a small project and then force the students to make massive changes to that project throughout the course of the year, but to me there is immense value to that. Even better would be if they had CS101, CS201, CS301 and the students taking CS201 would be forced to dig up their project from CS101 and work on that, CS301 would work on the code from CS201, etc. It's eye-opening how much you forget about your code after a few weeks, so it would drive home the notion of commenting, writing easily readable code, etc.