What's wrong with this code, really?
cvmountain.com
cvmountain.com
When you first start programming, you generally have no idea what the hell you are doing. You learn all these strange, abstract concepts best, by experimenting.
It's easy to dismiss people "jiggling things around until they work", as lesser, more inefficient or just plain bad programmers. Just remember that you were once like that too.
I think there should be more patience among the experienced, for the programmers who are still learning the basics.
They don't have a mind of their own, and programming isn't magic. Opaque languages, libraries and APIs don't help the situation either. I wonder how many programmers start out under the assumption that computers are more-or-less magic?
Even if they have no idea how bad they are, which they usually don't.
The lower-level world can be quite a bit more unseemly, what with the unpredictability of occasionally marginal voltages or power supplies, of RAS recovery and the occasional RAS and ECC errors, of sections of system buses lacking ED/EDC protection, the "fun" that is radioactive RAM and embedded alpha emitters (particularly "entertaining" if you don't have solid EDC), bus transients, and a host of other hardware gremlins.
Looking at the lowest levels of these boxes, I something marvel that these modern computing boxes even work at all, much less as well as they do.
The likelihood of some cosmic ray flipping a bit of a counter variable in some loop's sub-millisecond lifespan is just so unimaginably small you can count it impossible.
Computing education should be started in Scheme or Python, without all that accidental complexity-- "public static void main(String[] args)"-- littered around the first program. To absorb all of that requires more than should be expected before people can write a first program. JavaSchools get people used to magic incantations that create a culture where inadequate knowledge is the norm.
After learning the essentials, one can move on to languages with powerful type systems (Ocaml, Scala, Haskell) and the discussion of OOP and when it's appropriate and when not.
People should be starting with simple programs and knowing exactly what they do, rather than creating classes without a clue what a class is and why the concept is important (much less where not to use object-oriented programming).
The argument for starting CS in Java is that it makes grads more employable. Frankly, I would never hire (for a programming position) someone who took one CS course and stopped taking more. The whole raison d'etre for starting CS education in Java is bogus, and I think the practice is actually harmful. As much as I hate the factory metaphor in education, let's "produce" 20 programmers who actually understand what they are doing instead of 200 who don't have a clue.
Can you name a school that teaches a student only one CS course expecting them to be a programmer?
> This was not a problem. This was not either, for me. It was for ~80% of the class though.
> Language syntax had little to do with conceptual understanding. A uselessly verbose, initially incantational syntax adds useless friction, which just makes many people minds grind.
My experience in teaching languages is that Java and C# were atrocious grind generators whereas Python, Ruby and C were much smoother. JavaScript sits in the middle.
This is never explicit, but the reason many schools make Java the default language is because it's more "employable" than Python or Scheme.
If you're a CS major, you've probably been exposed to Lisp or Python in your AI class, C in your OS course, and ML and Prolog in your PL class and the "default language" matters, from a CV perspective, a lot less because people've been exposed to more languages. The default language matters in this way for those who take one or two CS courses and then try out for programming jobs in the future.
Also,I'm currently teaching myself Haskell as a hobby, and I'm quite regularly going through the "jiggling things round until they work, and then discovering a far more elegant way of doing the same thing" process. I don't feel bad about this - my professional programming days are many years behind me now, and I find that knocking something up and then revisiting it when you've understood more is a great way to learn a language (just not a particularly good way to develop professional applications).
Sure. But you're not ready for much more than an internship in the software industry (if even that), if that's the phase of programming you're at. The point is that you aren't yet ready to write production code if that's your approach.
But if it's starting to use some advanced OO concept, a new protocol or similar, experimentation and learning-by-doing is a very valid approach, even if you are highly experienced.
If it was not alright to code something without understanding every single layer from the highest to the lowest level, not many programmers would get anything done.
Except maybe Linus Thorvalds or Steve McConnell :-)
"Wow, this code is messed up. This whole project is messed up. Wait, there's a weird edge case the author must not have considered. Hmmm, that has some side effects too. Crap, this can't be on purpose, but a bunch of other code touches this thing. Does any of it rely on this behavior?"
With any luck, after burning a day studying a thousand lines of code, you realize that none of it does anything useful, and the bug you were chasing is hiding somewhere else. (Ok, I'm a little better at debugging than that, but you see the point.)
A novice was trying to fix a broken Lisp machine by turning the power off and on.
Knight, seeing what the student was doing, spoke sternly: “You cannot fix a machine by just power-cycling it with no understanding of what is going wrong.”
Knight turned the machine off and on.
The machine worked.
But thats not the point - while (not empty) is not iterating over the loop, so there is no danger of corrupting the iterator.
while(collection.Count > 0)
{
collection.Delete(0);
}
.. which is a working implementation of clear, though probably superfluous.and...
int i = 0;
while(i < collection.Count)
{
collection.Delete(i);
i++;
}
which does try to walk along the list and is broken.Also, .Net has a specific exception to stop you modifying a collection while an enumerator is walking along it. Google for "Collection was modified; enumeration operation may not execute"
for (int i=0,j=this.MyControl.TabPages.Count; i < j; i++) {
this.MyControl.TabPages.Remove(this.MyControl.TabPages[i]);
}for (int i=this.MyControl.TabPages.Count - 1; i > 0; i--) { this.MyControl.TabPages.Remove(this.MyControl.TabPages[i]); }
Imagine the count is 2. The first iteration you delete item 1, the second you delete item 0, and then the loop exits.
EDIT: Actually, as someone else pointed out, it's clearer to use a while loop that deletes the 0th item until the collection is empty.
I think what you meant was:
for (int i=this.MyControl.TabPages.Count; i > 0; i--) { this.MyControl.TabPages.Remove(this.MyControl.TabPages[i-1]); }
for (int i=this.MyControl.TabPages.Count - 1; i >= 0; i--) {
this.MyControl.TabPages.Remove(this.MyControl.TabPages[i]);
}
Though a simple while loop is much easier to follow, even if its less efficient than removing the elements in reverse.while (MyControl.TabPages.Count > 0) { MyControl.TabPages.RemoveAt(MyControl.TabPages.Count-1); }
For loop are nothing more than while loops with:
(1) an assignment (int i = MyControl.TabPages.Count in this case)
(2) an extra command (i-- in this case) added to the end
Regarding for vs while, I find the choice is important only in the intent they emphasize: while puts emphasis on the condition, whereas for puts the emphasis on the iteration. I think in this case the condition (that the list is not empty) is deserves more emphasis than the iteration through the elements of said list - hence why I find the while version to be more readable. YMMV and all that :)
Ten years ago, sure, but nowadays I trust the compiler to do this for me ;-)
It's TFA's method, except broken (or not fixed, word it as you prefer).
For you case deleting from either end ought to be fine, but you've made the other implicit tradeoff because merely accessing items in the middle of a linked list will be slow. In the case of something like a JavaScript array, removing from the front is 80% slower than removing from the end:
Same deal with Python lists. From the Python spec:
http://docs.python.org/tutorial/datastructures.html#using-li...
It is also possible to use a list as a queue, where the first element added is the first element retrieved (“first-in, first-out”); however, lists are not efficient for this purpose. While appends and pops from the end of list are fast, doing inserts or pops from the beginning of a list is slow (because all of the other elements have to be shifted by one).
But, honestly, for the majority of stuff in Javascript, I'd be surprised if some kind of hybrid hash-map / ordered skip list weren't being used instead. Ordered skip lists can be about as fast as a binary tree, without the costs associated with rebalancing, and a lot less complexity. You'd have some tradeoffs in memory usage depending on how you want to tune your skip list, but given the absurd memory requirements for modern software, that doesn't seem to be a consideration amongst programmers anymore.
So ... I accept that removing items from the head of a list in these higher-level languages is (a lot) more expensive. But I still don't get why.
One way is to leave the indexes intact and keep an offset around, so you may map the nth logical index to the nth physical one. (Add 1 on a shift, subtract 1 on an unshift.)
But if you wanted this behaviour, there are more efficient ways: http://en.wikipedia.org/wiki/Circular_buffer
So in the common case, you are in fact looking at a big memcpy every time you remove an object from the front, unless you do some magic with keeping track of a nonzero offset in your C array. V8 does that magic in some cases but not others, as far as I can tell.
Parent has a point for arrays/arraylists, though, you need to copy everything after the element.
If you recalculate it right there, you've actually done nothing in terms of the algorithmic complexity. If you defer it either until it's needed or until you next enumerate the list, then you get to O(1) in the case of individual removes at the end (as long as they're interspersed with other operations), but you're still O(n^2) for removing the entire list starting at the tail.
In terms of performance, another consideration may be important here: invalidation and redrawing of the UI. Controls like tab pages may update the UI for every modification of the tab collection (unless updates have been suspended). Removing from the end will look slightly more pleasant than removal from the start in this case.
for (Iterator it = container.iterator(); it.hasNext(); )
if (!predicate(it.next())(
it.remove();
It's more ugly and error prone if you've got to juggle an index, though. for (i = ctr_size(container); i > 0; i--)
if (!predicate(container, i - 1))
ctr_remove(container, i - 1); for (i = ctr_size(container); i--; )
if (!predicate(container, i))
ctr_remove(container, i); filter predicate xs
:)I always love the problems on SPOJ where they give you the number of cases up front, because in Haskell you can almost always throw out that value. Your map function knows when the list is out of elements.
CollectionUtils.filter(collection, predicate);
Of course aside from being a bit more verbose, predicate needs to be an object (often a singleton), because you can't pass around functions.var filteredCollection = collection.Where(x => predicate(x));
var filteredCollection = collection.Where(predicate);
I agree that C# is a fundamentally usable language. for (i = container.size; i-->0;)
if(deletep(container, i))
container.remove(i); while(container.Size > 0)
container.Remove(0);
No need for variables. container.clear();
No need for looping :)The iterator may become invalid if the collection it derived from changes.
Iterator delete methods are crazy to begin with since iteration does not correlate with deletion. But anyway it is not clear where the iterator's cursor will point after you delete the current element. You could end up deleting every second element in the container.
In the systems class I TAed, when students had memory corruption problems, removing items while iterating over a linked list was at the top of my list of things to look for.
A better question would be, there's tremendous deadline pressure, the company is in imminent danger of losing a giant deal if we don't have working code for some demo, and you have three features to implement by Monday afternoon. Do you write a new feature immediately, open a ticket for refactoring this loop and then write a new feature, or rehearse your explanation to the big boss that over the lifetime of the software, rewriting the code before adding a new feature was more important?
http://raganwald.posterous.com/javas-comb-over
Just kidding, but trying to make the point that "what do you think of this code" is a little obvious as an interview question.
In otherwords showing confidence, humility, communications, and that they have a clue about quality.
The point of code review is to catch monsters like this when they are written. If that doesn't happen, what's the point?
This code works and, as he says, was produced by a coder under time pressure. You show me a system and pretty much I'll show you a system that has poor (but working) code in it produced by a competent developer under pressure.
And code review is a useful process but it's no guarantee that issues will be caught any more than system testing or user acceptance testing.
And yet the world keeps on spinning and all is not lost.
I agree, you'd hope this was caught, but if someone gave that answer in an interview even aside from the tone, I'd wonder how much real world experience they had.
while( this.MyControl.TabPages.Count >0)
{
this.MyControl.TabPages.Remove ( this.MyControl.TabPages[0] );
}Or is it only me?
Addendum:
I would also add that even as a junior program, Clear() was easily learned within the first few minutes and usually when you have to use a hack like this it's because something has gone wrong. I wouldn't necessarily chalk this up to inexperience or deadline it could honestly be there was a bug and this was the only way to get it to work.
Even if I had to avoid .Clear(), there are immediately-obvious, better ways.
while ( !thing.Empty() )
thing.Remove( 0 );
In fact, the code isn't just bad; it's risky. What if someone changes "int" to "uint"? It wouldn't even infinite loop / crash... It would remove exactly one element!And you don't know what's going into those tabs. That cliché shouldn't be used to excuse positively brain-dead choices.
I'll bite: in arrays, front deletion (like back deletion) can be done in O(1) time. Keep a pointer to the first element, and just increment it upon front deletion (and free the element if necessary). Obviously, you may want to be smart to avoid memory use getting out of hand.
And you don't know what's going into those tabs.
If the array stores references (pointers), which you'd expect with tabs, that is orthogonal to the discussion. Whatever is going into those tabs, a pointer is a pointer.
Actually that's how they often do array-based queues and deques. To help with memory use they let the tail pointer overflow through the end (so you could have tail at index 2 and head at index 8).
Besides, if you really cared about efficiency, you wouldn't be using growable containers... =)
roel_v is correct to point out this misunderstanding.
I can't see that. If i is a uint, at the end of the first cycle, i-- changes i to the maximum uint, then i++ changes i to 0, then i < Count compares Count to 0. It is identical to, if uglier than, the following:
while ( 0 < this.MyControl.TabPages.Count )
{
this.MyControl.TabPages.Remove ( this.MyControl.TabPages[0] );
}Modifying a loop variable inside a for() loop is generally a very bad idea, that's a red flag for me.
For one, for loops have invariants.
Next up, you are altering the loop bound in the loop. I do not care if the code works, the code is hard to reason about when it goes all non-linear like that.
Finally, there is a goddamn Clear method.
That is the kind of code you see the next morning and delete; hoping none of your peers review the version control log that closely.
There are a lot of reasons why someone doing difficult work with complex objects would use a loop to delete them, and using invariants is only possible if you have immutable data structures.
Doesn't justify anything that was done.
> Or perhaps the data structure is a vector of pointers in C++, in which case using the standard clear method would introduce a massive memory leak.
Doesn't justify anything that was done.
> There are a lot of reasons why someone doing difficult work with complex objects would use a loop to delete them
Then use a while loop, and make this explicit. While loops have tests, for loops have invariants. There is a reason both exist, and it's not purely historical.
> and using invariants is only possible if you have immutable data structures.
Incorrect.
readability < does it work
No. In a professional setting, readability ~= does it work.
The code presented works almost by accident. It is clear there is no thought put into it at all. It is confusing, and subsequent modifications could cause errors very easily. It is not acceptable code.
Of course, code needs to work. Of course, if this .Clear method is a memory leak you wouldn't use it. Of course, we need to consider these things. But there is no situation in which this code is acceptable. This code would not even warrant a passing grade in an "Introduction To Programming" class.
Yes. Exactly.
Wow, dude, maybe if you don't know what the words mean, you shouldn't argue about them.
To clean it up, I'd do 1 of 2 things: Either write a .clear() function, or rewrite it to start at the end and clear the items in reverse.
With the .clear() function, I can at least ignore it because it was tested and worked. (You do write tests, right?)
With it in reverse, it's something I've done numerous times because of how lists work in certain languages. I'd instantly recognize that it's going backwards because the list always starts at 0.
If I wrote it in reverse, I'd also write a comment about why it's in reverse, though, so that anyone else can instantly know why, as well.
If you're accessing the size of, say, a linked list, you end up sneaking an O(n^2) runtime because it has to re-count the size of the list every iteration to check for termination.
c.f. https://secure.wikimedia.org/wikipedia/en/wiki/Schlemiel_the...
for(i = list.size; i >= 0; i--)
list.removeAt(i);
Is itself a schlemiel algorithm on any singly-linked list...You're still going to run in O(n^2) if you remove from the back (as you'd do if you run in reverse).
Linked list are far more efficient if you build in reverse but remove in iteration order (just replace your head pointer with head.next)
I'd expect any decent linked list implementation to keep an integer with the current size. Java certainly does: http://www.docjar.com/html/api/java/util/LinkedList.java.htm...
Either way, we have just come up with 3 much clearer solutions in what I would guess is at most 5 minutes between us. I would guess we have the luxury of it not being 9pm at night and working for our jobs. I was happy to see the author include note of that rather than just call the original programmer an idiot for not knowing how to write maintainable code.
int c = this.MyControl.TabPages.Count;
for ( int i=0; i < c; i++ )
{
this.MyControl.TabPages.Remove ( this.MyControl.TabPages[i] );
}
Let's say you have 4 items. That'll go something like this: Original list: (0,1,2,3)
Remove 0 Item 0 removed, items (1,2,3) become (0,1,2)
Remove 1 Item 1 (originally 2) removed, items (0,2) become (0,1)
Remove 2 Only two elements left in the list (0 and 1), so there is no index 2 anymore...That said, I've learned that with an early stage startup where you're trying to iterate as quickly as possible in a desperate attempt to get somebody to care about your product, you often have to pick your battles. Just yesterday I came across this:
category_count = []
for i in range(10):
category_count.append( db.execute("SELECT count(*) FROM table WHERE category = %d" % i) )
For one thing, this iterate separately and then append to list approach in Python annoys me slightly (list comprehensions are so much cooler!). But far worse, it hits the DB 10 times instead of once, and no matter how small your site is you obviously can't be having that. How'd it get there? Who knows. It was written in the Django ORM, where the only way to do this is with a pretty obscure command like Object.values('category').annotate(count=Count('category')). At first we were picking up Django as we went, so at the time whoever wrote it probably had no idea that the values() or annotate() methods even existed, and the way it was written got something up on the page and working so we could decide whether or not we'd be throwing it out the next week. But, whatever, you come across something like this, go throw up, fix it and move on. And finding these kinds of issues puts into perspective smaller ones like using a for loop where you meant to use a while loop.tl;dr - Having the luxury of sexy-ing up your your code as described in the post is strongly dependent on the stage that the project/company is in.
for ( int i=0 ; i < this.MyControl.TabPages.Count ; i++ )
{
this.MyControl.TabPages.Remove ( this.MyControl.TabPages[i] );
i--;
}
Nice analysis into the thinking that went into creating such bad code. Took me a while to even see the i-- at the bottom.That said, I've been guilty of doing stupid things like this many times when I'm tired and just want the damn thing to work. Its amazing the kind of errors you make in situations like that.
The problem is that imperative programming is horrible for code-by-experimentation. You end up with code that works, but is hideously unreadable. Declarative styles can help greatly with this. Functional programming can be a big boon here. But I think we're going to need a fundamental shift soon in either tool quality (say, to automatically refactor that shit code into the most straightforward and readable way), or a new paradigm that will allow code-by-experimentation to always result in readable code.
/* Can't find a clear/remove method, don't have time to screw around with it now, might revisit later, might not. Sorry. */
Then it's two more hours of screwing around until they get it right. Then I show it to them on a notebook with a different DPI setting...
I would hope that in those kinds of situations I would remember to add a FIXME comment so that I would come back in saner times and make it nice.
this.MyControl.TabPages = new Array();
Try to find something like this when the problem you are confronted with is that a server has to be rebooted every few hours because it eats up memory.
for ( int i=0 ; i < this.MyControl.TabPages.Count ; )
{
this.MyControl.TabPages.Remove ( this.MyControl.TabPages[i] );
} while (this.MyControl.TabPages.Count > 0)
{
this.MyControl.TabPages.Remove ( 0 );
}Less than 50?
Then don't worry about the optimization until you have to port it to a PDP10.
No, way more than that.
No, over nine thousands.
while (this.MyControl.TabPages.Count > 0)
{
this.MyControl.TabPages.Remove ( this.MyControl.TabPages.Count-1 );
}In C# / .Net you can't remove an element from an enumerator while you're enumerating through it. You can remove the last element however, as it's the final loop the enumerator isn't used again so it won't throw an error. The original developer probably tried to remove it forward only first, encountered an error and wrote the code to loop through it backwards, using the random tweaking technique.
What's rather depressing is that a lot of developers I've encountered use the random tweaking methodology, instead of figuring out what's really happening.
The can't-modify-a-collection-while-enumerating-it issue only comes into play if you're actually using an enumerator (either directly, or as part of a foreach loop) - the code in the article uses a plain for loop along with indexing into the collection, and wouldn't run into the problem.
Rather, the primary "issue" is that without that "i--" at the end it only removes half the elements - after removing an element, all the following elements shift back one index, and so the very next element never gets removed.
When I see nasty code like that, I tend to stop parsing it fully and sniff out the intent. I think it's a form of bad code blindness (like banner ad blindness) my brain is protecting me from all the bad code I've seen. If I fully parsed all the really bad code properly I’d become a dribbling wreck. :) So I tend to look at it at a higher level instead to stay sane.
2. List elements are removed from the front, the code is essentially a complicated version of:
while (0 < this.MyControl.TabPages.Count) {
this.MyControl.TabPages.Remove(0);
}With this code snippet it's obvious that the intent is to remove all the elements. So it can be fixed with a .Clear();
From a higher level, you can see that the snippet smells bad, and will need some attention.
I didn't say I'm smarter than all the developers I encounter. I've just encountered a lot of bad developers in my time.
Why? I like to think library developers have my back. What's the point in an iterator, it its broken on the 1st thing I try? (Ok the 2nd thing; 1st I code a search through the list, then the teardown)
Iterators are also pretty much broken when 1) inserting into a list, 2) merging a list into a list, 3) deleting an item from a list.
Ok, they're pretty much broken for absolutely everything BUT searching lists.
I remember the profound disappointment I felt when Java 1 came out, and its iterators were this same lame junk.
So I never use them. They are born to create bugs like this one.
Writing code that does what it is supposed to do is often not the challenge of software engineering - but writing code that can be easily tested, refactored, altered and ultimately understood by other developers is the harder part.
The conditional statement used in the 'for' loop whose value can not easily be determined is not helpful and the i--; is 'unusual'.
In any case, it is more useful to code review the unit tests than the code itself.
for ( int i=0 ; i < this.MyControl.TabPages.Count ; i++ ) { try { this.MyControl.TabPages.Remove (this.MyControl.TabPages[i] ); i--; } catch(Exception e) { } }
Can this be simplified? If this syntax were used elsewhere in the program, but we didn't want to catch the exception in this particular case, should we copy-paste this, and remove the try block? Might there be a situation where preserving the syntax makes the program clearer?
What would be wrong with just setting a variable to the value of 'this.MyControl.TabPages.Count' outside of the for loop and refering to this?? ie;
var x = this.MyControl.TabPages.Count;
for ( int i=0 ; i < x ; i++ )
{
this.MyControl.TabPages.Remove ( this.MyControl.TabPages[i] );
}
as a quick fix, or if someone did not know while loops or clear function??When i reaches 2, two tabs have already been removed, so there is only one tab left. So in the loop body we then do:
this.MyControl.TabPages[2] // oh no!
In general, modifying a collection is a bit of a code smell, and a lot of iterator implementations will actually throw exceptions if you try it. var x = this.MyControl.TabPages.Count;
for ( int i=x ; i >0 ; i-- )
{
this.MyControl.TabPages.Remove ( this.MyControl.TabPages[i-1] );
}As a rule of thumb, don't ever rely on indexation in a collection if you do random deletes. Usually you'll just blow up your app gracelessly. Sometimes, epic failure ensues.
Through some feats of logic we might deduce that there's always a first element, though, until the collection is empty. So you might do this inside the loop: MyControl.TabPages.RemoveAt(0)
Needless to say, calling Remove when you have the bloody index (on IList collections that is) is counter-productive.
And I guess that code would be okay. I mean, if you head to phrase it : let x be the number of elements in the list, take out the head of the list that many times.
However, is it really the fastest way to clear a list? No. If we could access the class internals, we could just replace the store with a new empty array. Voila, O(1) clear and the garbage collector takes out the trash for you.
Generally, though, don't spend time worrying about implementation if you already have one available. When you've done optimizing all of your stuff (which is never the case), then you could go on to suggest changes to the standard library.
Clearing the entire array can usually be accomplished easily, but what if you want to remove only items matching some condition?
Looping backwards, perhaps?
for ( int i=this.MyControl.TabPages.Count-1 ; i >= 0 ; i-- )
{
this.MyControl.TabPages.Remove ( this.MyControl.TabPages[i] );
} filter predicate xs IEnumerable<T>.Where(Func<T, bool> predicate)
It's quite simple to use: var odd_numbers = numbers.Where( n => n%2 == 1 );Good grief. You are in a sorry environment. What are you doing there? I will fire anyone that writes code like that and checks it in, and then fire anyone who objects to me flagging it in a code review.
This code:
for i in 1.100: print i
There's nothing to talk about, it's crystal clear.
If it has a .Count() and a .Remove(), it should have a .Clear()
brlewis has the winning answer
At the very least, I would have hoped everyone had read through the article.
You might look at it for 2 minutes and figure it out, but that's 2 minutes too long for what's actually being accomplished. The problem is, if you were to come back and look at it again 6 months from now, it would take another 2 minutes.
The point of the article isn't really the method that was used to get to the result, but rather the fact that code should be made easy to read and understand, because +60% of the time is spent maintaining it. I usually tell this to newbie programmers, "code is meant for people to read, machines understand on/off".
Even if you need to borrow such a convoluted approach to clear a collection (as opposed to the more direct clear() method), there are simpler and more readable alternatives:
while(Pages.count > 0){
Pages.Remove(Pages[0]);
}As for your approach, I do like that better.