I am a high school student working on a C# project with a friend of mine with about the same skill level as me. So far, we have written roughly 3,000 lines of code and 250 lines of test code in a span of 100 commits. Due to school, I put off the project for a few months and recently I was able to pick it back up again.

By the time I had picked it back up, I understood that the code I had written was poorly designed, such as inclusion of excessive threads in the renderer, poor prevention of race conditions in the interaction between the emulated CPU, GPU, and game cartridge, as well as code that is simply redundant and confusing.

The problem is that I have not finished even the main functionality of my program, so I cannot truly refactor, and I feel discouraged to go on perfectly aware that my code's design is flawed. At the same time, I do not want to abandon the project; a substantial amount of progress has been made and the work must not go to waste.

I feel as if I have a couple of choices: to simply finish the functionality by working around the poor design and then refactoring once everything is working, halting everything and working toward untangling everything before the rest of the functionality can be finished, starting the project all over so that it is fresh in my mind again, or downright abandoning the project due to its excessive size (essentially "back to the drawing board").

Based on others' experience in such projects, what is the recourse in getting myself back on the right track? From answers on this site, the general consensus is that rewriting is generally unnecessary but an available option if the code cannot be maintained without excessive cost. I genuinely would like to pursue this project, but as it stands, my code is not designed well enough for me to continue, and a feeling of despondency detracts me from continuing.

有帮助吗?

解决方案

If I were in your shoes, I would probably try it this way:

  • first, finish the current project - at least partially - as soon as possible, but in a working state. Probably you need to reduce your original goals, think about the minimum functionality you really need to see in "version 1.0".

  • then, and only then think about a rewrite from scratch (lets call this "version 2.0"). Maybe you can reuse some of the code from V1.0. Maybe after sleeping again over the situation you come to the decision you could refactor V1.0 and save most of it. But don't make this decision before you do not have a "proof of concept V1.0" at hand.

A working program "1.0" is something you can show to others, even when the code is bad (which noone else will bother about, except yourself). If in the middle of creating V2.0 you realise you are running out of time, you still have V1.0 as a partially success, which will much better for your morale. However, if you do not finish V1.0 first, there is a big chance you will never complete V2.0 because when you halfway through, there will be a point where you are unsatisfied with the design again, and then? Will you abandon V2.0 again and work on V3.0? There is a high risk of running into this never ending circle, never coming to an end.

Better take this as an opportunity to learn how to achieve intermediate goals, instead of an opportunity to learn how to leave projects in an unfinished state behind.

其他提示

Finished IT projects, even faulty ones, are much better than unfinished ones.

Unfinished ones can teach you a lot too, but not as much as finished ones.

You may not see it now, but you get an enormous amount of value working with even faulty code.

My vote goes for finishing and then, maybe, refactoring - if needed. When you start working with more projects you will see that surprisingly often the part that "was meant to be refactored" stays untouched for years, while other pieces get extended.

From an employment point of view, in most cases, you will get more kudos for a finished project.

I would happily start the project over.

You're a student, and you're still learning. This puts you in a very different position than the question you linked to.

  • You have no professional responsibility for your code; if you were to delete the whole project right now and walk away, you would suffer no repercussions. This is a huge advantage for a developer. In the question you linked, those developers are trying to maintain software that people are paying money for, and even tiny bugs can cause big problems with customers, which makes it dangerous to write from scratch. You do not have that problem.

  • As a personal project, this is the perfect opportunity to try new things and learn from your mistakes. It's great that you recognize your old code has issues, because that means you've learned how to do better. Experience through trial-and-error can be invaluable for beginning programmers.

  • Your project is relatively small, and you basically have no tests. It could be as short a few nights to write 3000 lines from scratch, especially since you already have ideas about what to do better next time.

  • Working in a broken codebase will be a far bigger drain on your morale than a rewrite would.

As an aside, this may be a great chance to try writing a more modular program with unit tests. Doing so makes it much easier to understand the code if you come back to it after a long break, and helps to prevent errors you might accidentally introduce due to forgetfulness.

Finally, here is one opinion on the wisdom of sometimes taking steps backwards to take bigger steps forward.

You're probably still in the "learning fast" part of your development. There's a good chance that a few months from now, you'll find that your new and awesome design is horribly broken in ways you weren't even aware of when you started.

Get things done - this is the single most important thing you need to learn. Believe me, I know plenty of people (not just programmers) who are stuck in the "keep starting over from scratch since I've learned so much before I started on this project" loop. The problem is that it never ends - until you stop learning new things, which is not a nice place to be :)

It's also important for being able to actually finishing anything. Starting over is exciting, cool, fun... but if you only learn that, you'll never be able to finish anything. Again, that's not a very useful ability, and it actually doesn't feel quite as good as making something useful (or fun). Life is short, time is limited, and you can't just keep practicing with no tangible results - you'll go quite mad from that. If you find yourself unable to start working because you just can't decide which approach to take, you're already in the danger zone.

There will always be impulses to start over. Even in an learning environment, those are most likely a bad idea. That doesn't mean you need to carry every single prototype to a production-ready application, far from it. But you need to at least get to a working prototype phase - it will most likely help your morale, and it's a very useful trait for a developer of any kind. Get things done. And the fun part is, you'll quickly see that there's a million more fun or useful things you can do with your prototype rather than "rewriting it from scratch" - and in many cases, even refactoring it. Everything you do has a cost - at the very least, you could have been doing something else in the meantime.

I follow the "Make it Work, Make it Right, Make it Fast" ideology in software development.

  1. Make it Work: Write the core functionality, so that the project is usable. Do what you have to in order to make everything work, even if it means ugly code.
  2. Make it Right: Fix bugs and refactor the code so that it is easier to read, understand, and maintain.
  3. Make it Fast: Optimize the program, aiming for a good balance between speed, resource usage, and maintainability.

Right now, you haven't finished step 1. Make it work first, and then you can worry about how ugly your code is. Actually creating a working product is one of the hardest things for new developers to do, because they get so wrapped up in trying to make their code perfect the first time around, and so they get stuck in Optimization Hell and never actually get around to finishing implementing all of the features. You have to learn how to put aside all those worries about refactoring and optimization so that you can just focus on making the software work. Once you get some more experience, you'll naturally do more things right the first time, so less refactoring is necessary.

Keep in mind: premature optimization is the root of all evil (see this excellent Programmers.SE question for a good discussion). If you spend too much time thinking about how to make your code better, you get stuck in analysis paralysis. This kills the project. It's better to just get the project done before you start trying to optimize it.

To quote a great motivational speaker: just do it.

As always, there's a relevant xkcd:

xkcd optimization

Rewrite it. Non-working code has little value, and three thousand lines is not much code. It won't take nearly as long as it took to write the code you currently have, and it will be much better. I have often thrown out five hundred or a thousand lines of poor code, and often the rewrite is one-fifth as long.

Most of the notions around "don't rewrite" relate to large systems doing important things and undergoing constant changes to meet evolving requirements. Rewrites of complex systems in use rarely succeed.

Your situation is the complete opposite. You have a small application with no users and the only requirements are the ones you choose to implement. So go ahead and rewrite it.

Restarting from scratch is usually a bad move in real life projects, mainly because real life projects accumulate bug fixes that a newcoming developer is not aware of (e.g., see Things You Should Never Do, from Joel on Software blog.)

Neverthless, school projects don't inherit such history and usually start by coding and designing at the same time with partial knowledge of computing and lack of experience. I would consider your first version as a prototype, used as a proof of concept which has failed, and I would happily throw it away (see What are the differences between throwaway and evolutionary prototypes? for a discussion about throwaway vs. evolutionary prototyping.)

The right design has matured in your head and should not take much time to write it down in code.

I would respectfully disagree with suggestions that rewrite is a bad idea.

In the realm of software life cycle, it is generally accepted that the time and effort required to correct an error increases by an order of magnitude each layer in the life cycle. That is, if it takes 1 hour to correct an error at the requirements level, it will take 10 hours in design, 100 hours in coding-testing, and 1000 hours as a bug fix. Those numbers might sound outrageous, but they are industry accepted as being approximately correct. Obviously, less in a smaller project, but the general idea remains. If your project has a basic design flaw, then it is more appropriate to call that a learning experience and go back, review your initial requirements and redesign.

I would also encourage considering a Test Driven Development model using structured unit tests. They are a pain and initially seem like a waste of time, but their ability to uncover errors, especially when integrating with someone else's code cannot be over stated.

You lost me at this sentence:

The problem is that I have not finished even the main functionality of my program, so I cannot truly refactor, and I feel discouraged to go on perfectly aware that my code's design is flawed.

I believe in the principle (stated in Systemantics) that

  • A complex system that works is invariably found to have evolved from a simple system that works.
  • A complex system designed from scratch never works and cannot be patched up to make it work. You have to start over, beginning with a working simple system.

So, writing a complex system includes

  • Writing a simple system
  • Making sure it works

... i.e. the following steps:

  1. Write a bit of code
  2. Test it to make sure it works
  3. Write a bit more code
  4. Test again
  5. Etc.

Note that step 3 might involve:

  1. Write a bit more code
    1. Refactor existing code to prepare it for new addition
    2. Test the refactoring to ensure it still works
    3. Add the new functionality

Also, Step 2 "Test it to make sure it works" might involve some rewriting if it doesn't.

If I were building on a rotten code-base I'd be disinclined to add further functionality. So I'd be inclined to schedule something like "simplify the existing threading implementation" as the next piece-of-work to be implemented. Assuming that you have been testing as you go along, you ought to be able to treat this as a refactoring exercise. The success/exit criteria for this work-phase would be:

  • Source code is simpler
  • And no new bugs introduced
  • (And some old bugs eliminated)

Incidentally, "testing it to make sure it works" doesn't necessarily mean "unit tests" -- when the team structure is simple, you can test a (simple) system using (simple) system tests (instead unit tests).

One of the problems you are facing when running into a problem like this, is you are emotionally attached to the code you have written so far in some way or another.

A colleague told me he was writing a program while in university while he got lessons from a famous professor ( i believe it was Dijkstra). He asked the professor to have look at the code he had been working on for over a month. The professor asked him if he had made a backup, he replied no. The professor then deleted all of his code and told him to write it again.

He was pissed, but 3 days later he finished the program, with cleaner code and less bugs and less lines of code.

Try to make an honest estimate on what option is best in the time available for the project.

The 3 choices are

  • Delete it (completely, no looking back)
  • Continue working in the old design
  • Refactor code while you go.

I've been a professional programmer for over 10 years, and in my line of work I've come to conclude the last option is the most of the times the one chosen, but it is not always the best option.

It sounds like your skills have grown substantially in that time period. Perhaps working on that project contributed to it. Designing it again as a purposful learning experience would be fruitful.

Remember, this isn't something you need to deliver as a working program for a customer. It was written specifically for the practice. So, unless you want to practice having shipping issues and flawed delivery instead, I think you currently are in a developmental phase where working your "design muscles" would prove fruitful.

When doing so, focus on the design process and planning, and muse over your existing stuff and reflect on what you're understanding better.

Refactor. Refactor. Refactor! REFACTOR!!!!

Honestly, no matter how experienced you are this is a common problem. You wrote code, you learned something, and you want to use the new knowledge on the old code. You want to measure the old code against the new knowledge.

That just won't work. If you do that your application/game will never be completed. Instead, try to allocate your time on the project so that some of your "tasks" are to refactor out the bad code. Let's say, for example, you had a horrible method of saving the game. Keep working on the game itself, but find some time to refactor (replace) that horrid method. Try to keep the refactors small, and it shouldn't be a problem.

When you get to a major part of the application that needs a refactor then you're doing it wrong, seriously. Break that refactoring down to smaller parts. Try to spend seven hours writing code, and one hour refactoring.

When you're done with the project and you hit feature freeze, then you can spend some massive time refactoring. For now, get the game done, but still try to refactor some. That's the best you can do.

There will always be something to refactor.

I'd say it depends a bit on what kind of code you have now and where exactly the problems lie. I.e., if your foundations are good (proper class design in the most parts, good decoupling/cohesion etc., just a few bad choices like the threads you mentioned), then by all means get a bare-bones 1.0 out and then refactor like there's no tomorrow.

On the other hand, if it's really just a bunch of ugly slapped-together text files somehow passing the compiler, with no discernable structure etc. (in other words, a proof-of-concept learning-on-the-job prototype), then I'd rather delete it and start over.

On your next version, do an agile approach: set yourself a fixed repeating timeline, like 2 weeks or whatever fits your schedule. At the beginning of each chunk (let's call it a sprint), set yourself goals that are achievable in the 2 weeks. Go ahead and do them, try your damndest to make it work. Set your goals so that after each 2 weeks you have something that can be shown to a friend, not just some abstract internal work. Google "scrum" and "smart goal". This is all very easy to do, if you're alone, just a piece of paper with a few quickly jotted-down bullet points.

If you can do that, then starting over is good. If you know deep inside that after starting over you will likely end up where you are now, anyways, then stick with your code and make it work.

You have to put yourself in the employers shoes.

To most recruiters, you'd be the most valuable if you go this way : - Finish it off with 100% tests on the new code you write. - Add tests for the old (bad designed) code. - Refactor it to achieve what you wanted as a design.

Do all that with a good versioning process, branches and tags.

Rewriting is often a sexy idea, but it's often an idea that newcomers have which is kinda dangerous in real life. Showing that you're more willing to improve the quality of work you already did rather that starting from scratch is a good sign that you're a serious person.

You can obviously rewrite it though, but make it another project then.

Just to add some of my past experience into the mix. I have been working on a side project for over a year now, whenever I get a spare few minutes. This project went from a simple testbed to a static class to a object orientated slim lined design it is at now.

Throughout all of these changes I have kept the same functionality of the code, excluding bug fixes and performance benefits (needs to be as fast as possible). The same code base although refactored has stayed the same and as such I have improved it massively without having to rewrite the code from scratch and waste time.

This has meant that I have been able to improve the code at a faster rate than I would have been able to before. It also meant as I was going it was easier to tell what needed to be removed, i.e. unnecessary classes, and what could stay easier than rewriting with the old idea still in my mind.

Therefore my advice would be to refactor your code and move on from there making improvements as necessary. Although do remember if you do decide to rewrite from scratch you should take the good ideas of the old project and look at them closely to see where they are flawed. I.e. don't just copy old code to the new project.

The answer depends on something which I like to call the "complexity ceiling". As you add more and more to a codebase, there is a tendency for it to become more and more complex and less and less organized. At some point, you will hit a "complexity ceiling", at which point forward progress becomes very difficult. Rather than trying to keep moving by brute force, it is better to back up, reorganize/rewrite, and then continue moving forward.

So is your codebase so bad that you are nearing a complexity ceiling? If you sense that it is, take some time to clean up and simplify before proceeding. If the easiest way to clean up is to rewrite some parts, that is fine.

Neither? Both?

Continue onwards, spend some time on revising the existing design and some time adding new functionality.

If you have racy interactions, that might be a good place to start ("excessive threads in renderer" sounds like an inefficiency, rather than something that would cause correctness issues). See if you can figure out either a general way of getting away from races (and possibly deadlocks), or at least multiple specific ways of doing so.

I don't know to what extent things like deadlock and race detectors are available for C#, but if they exist, they may be useful in identifying what problems there are and verifying that your fixes are working.

I find that, in general, I'm more motivated to fix problems with code that does something useful, rather than throwing it away and starting, again, from scratch. There are cases where scorched-field (in analogy to greenfield and brownfield...) development is more satisfying, but that's usually for things where the existing approach is so way out from where it should be that it is no longer even wrong.

If your code is modular then you should be able to finish the rest of the code around the badly-written components then re-write the badly-written components without affecting the rest of the code.

In that case, it's up to you when you re-write the badly-written components. If the badly-written components are so badly written that the finished code won't work with them as they are, then you'll need to re-write them before you can finish the rest of the code.

The first question you should ask yourself is "how bad is the flaw?"

What exactly did you do wrong?

If something is so bad that you will waste tons of hours trying to work with the issue, exposes sensitive data (passwords/credit-cards), or causes a bad vulnerability, then maybe you should edit it, but most of the time you can take what you have, finish it up, and fix the issue later.


Programmers love improving their applications, wanting them to be "the best that it can be," but that's not the correct approach.

IF you release your application as soon as possible, even if it's not 100%, bugs adn all, then you get FEEDBACK from others, while having time to fix the bugs and other stuff for version 1.1. Other people will be able to help you make the application so much better, and you might have wasted time doing something that people might have disliked.

So in your case, get it out there, get some feedback, and then you can structure version 2.0 with all the changes, and fixing the flaw you have.


Another thing to remember is that we are constantly improving. There is a saying that if you can look at your code from a year ago, and see that it's bad, that means you're still improving, and that's a great thing.

It depends on how messed up your code is.

  • If you made real n00b errors which make your code overly complex or verbose, then I would recommend to rewrite those parts.

  • If you think you have serious bugs which you'll have to fix anyway, then write some unit tests that can check whether you've fixed the bug (...since you can't yet launch the program). It's better to fix bugs early, and it's definitely better to fix bugs while you still remember what the code does.

  • If you don't like what the methods are called, or which class they belong to, or little things like that, then just use the IDE. (Remember, this is C#, not some javascript, python, perl, php, etc.) When you're working on code that uses the affected component, and you have a clear picture in your head of what that component should be doing, then refactor it if your IDE makes it painless.

Otherwise, get it working and hone your skills on the next project.

As others have suggested, as this is a personal project, not (yet) a professional project, I would seriously consider a rewrite.

However, you can leverage the scientific method here, by performing an experiment. First, conceive a hypothesis. Mine would be, "it's probably time for a rewrite." In order to save time, reduce the cost of failure, and somewhat limit a priori bias, before you do any more programming, decide a length of time ("time box"). I would suggest perhaps 4 wall-clock hours. Also decide upon a methodology for evaluating the hypothesis. Since the stakes are low, I would suggest as a methodology, simply ask yourself: "am I glad I did this?" Now begin the experiment. After your chosen time box, what is the evaluation of the hypothesis? e.g. From my example, are you glad you started rewriting now that you have spent 4 hours doing it? Then it's probably the right choice.

You can ask for all the advice in the world, but there's nothing quite like testing out a hypothesis empirically.

A few reasons to consider a selecting a rewrite as your experiment:

  • In my experience, it's usually more fun. A big reason rewrites are often not selected in professional situations is that fun is far from the sole criterion. But you're still relatively new to coding, so fun is probably a pretty important criterion, and also an indicator of other factors that may be intangible for you at this early stage (fun suggests you will learn more, etc.).
  • Your experience and probably a nagging sense of conscience seem to be telling you that a rewrite is a worth considering. Listen that voice, if there is one. Even if you don't follow it's advice, at least listen. In the future there will be more external voices, and you need practice listening to your own good sense.
  • I suspect with a rewrite, you are more likely to modularize your code more. When you create modules, you have more reusable and reliable components you can leverage for other projects. You may even find that in the end, what seemed like a mere module of your main project turns out to be the most interesting and valuable piece of code you write.

Finishing something is a good idea, but not in a vacuum. If you begin a rewrite, then you could elect to finish something by finishing a particular sub-module. You could set that as a first goal, after the above experiment. Finishing doesn't have to mean finishing your initial main project just yet. After you finish a module, finish another, etc., until you finish rewriting the entire scope your original project, if you like.

许可以下: CC-BY-SA归因
scroll top