views:

192

answers:

9

I'm working in a 12 year old code base which I have been the only developer on. There are times that I'll make a a very small change based on an intuition (or quantum leap in logic ;-).

Usually I try to deconstruct that change and make sure I read thoroughly the code.

However sometimes, (more and more these days) I just test and make sure it had the effect I wanted. (I'm a pretty thorough tester and would test even if I read the code).

This works for me and we have surprisingly (compared to most software I see) few bugs escape into the wild.

But what I'm wondering is whether this is just the "art" side of coding. Yes, in an ideal world you would exhaustively read every bit of code that your change modified, but I in practice, if you're confident that it only affects a small section of code, is this a common practice?

I can obviously see where this would be a disastrous approach in the hands of a poor programmer. But then, I've seen programmers who ostensibly are reading the code and break stuff left and right (in their own code based which only they have been working on).

+1  A: 

I'm most likely not the intended target of this question, since I have only been a professional developer for a few years now. However, I'll toss my 2 cents in anyway.

I do this ALL the time. When I'm under a tight deadline i get the code working reliably no matter if I understand the whole thing or not.

I generally try my best to go back later and look at what I did - usually on the weekends - and update the code to reflect any bad decisions with the copy/pasted code. But the deadline is king, especially if there are penalties in the contract for missing it. You can always send out an update that fixes any problems later.

Ariel
Do you often find, when you got back and review that code, that you've created a bug-inducing sideeffect (as opposed to just ugly code that could be cleaner).
Clay Nichols
+2  A: 

Sometimes changing code and testing helps you understand the code. Of course you are always treading on dangerous ground when you make changes you don't understand. I think your success is not only based on your "experience", but also the stability of the current code.
You can make changes to well-written code easily. But as time goes on, this practice will catch up with you.
If you look at code and you don't understand it, now is the time to figure it out and document it. Otherwise, every other programmer is going to have to figure it out when they have to make changes. Better to do it once, get it right, and save time & hassle for everyone in the future.

Mike Lewis
A: 

Hey, at least I test it.

Robert Rossney
+3  A: 

Once you're wrapped legacy code into a DOUBLE safety blanket of testing (good unit tests AND good system/integration/regression tests, with excellent coverage from EACH of the two layers), relying on tests to catch misconceptions IS an effective short-term tactical approach. This is part of what makes adding tests to a legacy codebase a high-ROI strategy: it enables you to make urgent bug fixes or add burningly-urgent features faster, by giving you reasonable confidence in the soundness of changes done without thorough understanding of the intricacies of the existing code.

However, as other answers hinted at, such changes ARE accumulating technical debt; as for any other debt, the sooner you pay it off, the better off you'll be in the long run. In this case, the "paying off" is not just about documentation of the internals, but it can often be in refactoring the codebase for clarity and malleability -- really good tests will help you do such refactorings with confidence, too, just as they do for bug fixes and functionality enhancements of the legacy code.

Alex Martelli
+1 for the technical debt
ojblass
+1 more for "technical debt". IT's high time we started coining our own buzzwords (especially ones with real meaning)
Clay Nichols
A: 

Constantly!

Charlie Somerville
A: 

You want your code to be made up of small pieces that can be tested and understood in isolation.

Each small piece should do one thing and do it well, whether that piece is a function, and method, or a class.

You want to be able to compose larger pieces of functionality out of these small pieces, in such a way that each composition, no matter how complex internally, remains simple at the level of abstraction of that functionality.

In other words, even at the high level, we should still be able to describe complex internals in tems of simple externals.

This is what Andrew Koenig means when he says "Abstraction is selective ignorance". By intentionally giving up knowledge of how something wmay work internally, we can consider not how it works,but what it does.

Let's give a short example. At the high end, we might say, "this class find that smallest int in some data structure". That tells us what it does, not how it does it, and at this high level of abstraction, that's all we care about.

We have a something that does something, and its modular, we can replace it with anything else that does the same thing, no matter how does it. That's having a public interface.

Now at a lower level, it may be that how this works is that internally it's a heap, or a priority queue, or whatever.

And those things may be implemented in terms of trees, or self-balancing trees, or even (sub-optimally) a linked list. A linked list would be a sub-optimal implementation, but as long as it did that same thing, we wouldn't really cafe, because if the suboptimality got back enough to slow down our program, we could swap it out for a better implemenation with the same interface.

And those things are implemented in terms of tree traversals (Pre-Order: always go in the order left, parent, right). And those things are implemented in terms of simple operations on nodes.

And here's the important part: because the rest of out app has no reliance on the internals or side effects of that module, the swap out of a poorer implementation for a better one changes none of our other code, it just speeds things up overall.

Each layer communicates only with the layers immediately above and below it, so that each layer can be replaced. Visually, it looks like circles inside circles inside circles, not like the overlapping of a Venn Diagram.

If you're having to rely on intuition, it suggests that your code has side-effects or that it has overly broad interfaces to other modules, or that instead of interfaces at all, instead of being code that is built up of self-contained, non-intersecting, modules, you have overlaps and intersections and fragile code. That you don't have it broken down into pieces simple enough to understand.

(Crap, it's late,and I fear I could have broken down this explanation better. I'll be back to edit this.)

tpdi
+3  A: 

It has happened to me once in a while. But I'd like to emphasize a point which another person has mentioned in their comment.

  • Whatever parts you do understand,try to document it. You may save yourself and others a lot of time the next time you make another change.

And, for my two cents worth...

  • You've mentioned that you 'test' your work and compare it against the previous results. I have read a few years back a guy trying to argument that the most valuable thing a company can own is not code itself but the tests (ie: JUnit). He said that throughout the life-cycle of a software the code will be changed often - from small fixes and enhancements to complete re-writes. With maturity and experience I understand what he means now and try to use his advice. With test cases, you can prove that your new work is functional and doesn't break anything. My point is... try to save and reuse any test that you may have. Some programmers have a tendency sometimes to trash those tests. They will become extremely valuable later on when other programmers make changes as well.

Happy code changing,

Jeach!

Jeach
A: 

No, I never do this. I expect my code to be 100% logical and free of bugs, and it will be read by other people for years to come. The entire concept of not fully understanding the context of my code seems dangerous. And reading code should be the fastest way to determine the exact operation of the system. If this isn't the case then I'd suggest there's something wrong with the quality of the code.

While I'm reading I'm also learning. I think being darn curious about the ongoings of the system I'm working on is an important part of my professional standards. With this curiousness comes the capability to evolve the system and improve it. If I couldn't do that I think development would get dull really quickly.

krosenvold
+1  A: 

If you change a method, you should test it thoroughly including the methods that call this method.

fastcodejava