views:

1183

answers:

14

The following code was produced by a consultant working for my group. I'm not a C++ developer (worked in many languages, though) but would like some independent opinions on the following code. This is in Visual Studio C++ 6.0. I've got a gut reaction (not a good one, obviously), but I'd like some "gut reactions" from seasoned (or even not so unseasoned) C++ developers out there. Thanks in advance!

// Example call
strColHeader = insert_escape(strColHeader, ',', '\\'); //Get rid of the commas and make it an escape character

...snip...

CString insert_escape ( CString originalString, char charFind, char charInsert ) {
    bool continueLoop = true; 
    int currentInd   = 0;

    do {
     int occurenceInd = originalString.Find(charFind, currentInd);

     if(occurenceInd>0) {
      originalString.Insert(occurenceInd, charInsert);
      currentInd = occurenceInd + 2; 
     }
     else {
      continueLoop = false; 
     }
    } while(continueLoop);
    return(originalString);
}
+3  A: 

CString has a Replace() method... (that was my 1st reaction)

I have seen a lot of bad code, and lots worse than this. However, not using built-in functionality when there's no apparent good reason not to is... poor.

Nick
No, the code works - the original string isn't modified because it's passed by value, but the modified string is the function's return value
Adam Rosenfield
Good call, missed that on first pass. He does have an error on the Find result, so the code is wrong, but not in the way I initially thought.
Nick
Yeah, but he could have made a copy by doing something like CString c( orig_string); c.Replace(...)... so there is still no reason to rewrite the Replace() method.
ceretullis
+17  A: 

hmm. I think

CString strColHeader;
strColHeader.Replace(",", "\\,")

would do just as well.

I don't like the code, I tend to break from the while loop instead of having an unnecessary bool 'continue' flag. That goes double when he could have used while (occurenceInd != 0) as his loop control variable instead of the boolean.

Incrementing the counter also relies on "+2" which doesn't seem immediately understandable (not at a quick glance), and lastly (and most importantly) he doesn't seem to do comments.

gbjbaanb
Well, in fairness, he did comment the usage example; only, like many other comments, the comment is not incorrect and misleading. It also could be a good example of why you should not use comments, but write self-descriptive code instead.
Nick
Except that CString::Find returns -1 when it can't find the character, not 0.
Eclipse
the original code uses ">0", I didn't want to break undocumented behaviour by fixing it :-)
gbjbaanb
It just shows why it's bad to do something the hard way, when there's a built-in way to do the same thing.
Eclipse
A: 

The Code written will indeed work, as it is returning the new string. It's just forcing the extra step of then setting the old string = to the returned string.

Like others have said, built-in functionality on strings is more efficient and less error-prone than re-inventing the wheel.

SauceMaster
A: 

Looks alright, if a bit, I dunno, hack. Better to use the library, but I wouldn't go and rewrite this routine.

Paul Nathan
A: 

This is in Visual Studio C++ 6.0.

Gut reaction: blech. Seriously! The C++ compiler shipped with VC++ 6 is known to be buggy and generally performing very badly and it's 10 years old.

@Downvoters: consider it! I mean this in all seriousness. VC6 is just comparatively unproductive and should not be used any more! Especially since Microsoft discontinued its support for the software. There are cases where this can't be avoided but they are rare. In most cases, an upgrade of the code base saves money. VC++ 6 just doesn't allow to harness the potential of C++ which makes an objectively inferior tool.

Konrad Rudolph
If there was control over crappy tools, it would have been exercised. Let go, move on.
Josh
you were probably downvoted for offtopic.
Dustin Getz
@Dustin: He asked for a gut reaction. Ok, seriously. This *is* the main issue here!
Konrad Rudolph
"Off topic"? The post mentioned VC6, i.e. a compiler from the last century. So, while I understand the idea was to comment the code, Konrad's anwer, while not *the* answer, added a valid point, which was as worthy as "Hey, the do...while loop is uncool" or other zero-point answers I saw above.
paercebal
A: 

Looks like people have tackled some of the functionality aspects of this code for you, but I'd suggest staying away from variable naming like you've employed here.

With the exception of UI controls, it is generally frowned upon to use Hungarian notation. This is more important with numbers...for example:

I declare:

float fMyNumber = 0.00;

And then I use that in my entire application. But then, later, I change that to a double because I realize that I need more precision. Now I have:

double fMyNumber = 0.00;

It's true that most good refactoring tools could fix this for you, but it's probably best not to attach those prefixes. They are more common in some languages than others, but from a general style perspective, you should try to avoid them. Unless you're using Notepad, you probably have something akin to Intellisense, so you don't really need to look at the variable name to figure out what type it is.

Ed Altorfer
A: 

There is always a better implementation. If you are using the function as an example of the consultant not being very good you might also want to consider that while they did not know a function that already existed they might have experience and understanding of project construction.

Software development is not just about the perfect function but also how good the architecture of the whole thing is.

Phil Hannent
+2  A: 

If you're wanting an evaluation of this developer's C++ skill level, I'd say this demonstrates the lower end of intermediate.

The code gets the job done, and doesn't contain any obvious "howlers," but as others have written, there are better ways of doing this.

JohnMcG
A: 

I always worry when I see a do .. while loop; IMO they're always harder to understand.

Jackson
I never learned the importance of pretest loops until I began to learn PIC and MIPS assembler. You think it's hard to understand in C++?
rodey
@rodey - don't you mean post test loop?
danio
Initially I found do ... while loops confusing but if that's what you want to do, then that's what the code should do rather than pre-testing something that you know will be true first time through.
danio
+11  A: 

There's an off-by-one bug sitting there right in the middle: Take a look at what happens if the first character is a comma: ",abc,def,ghi": I'm assuming the desired output would be "\,abc\,def\,ghi", but instead you get the original string back:

int occurenceInd = originalString.Find(charFind, currentInd);

OccurrenceInd returns 0, since it found charFind at the first character.

if(occurenceInd>0)

0 isn't greater than 0, so take the else branch and return the original string. CString::Find returns -1 when it can't find something, so at the very least that comparison should be:

if(occurrenceInd >= 0)

The best way would be to use the Replace function, but if you want to do it by hand, a better implementation would probably look something like this:

CString insert_escape ( const CString &originalString, char charFind, char charInsert ) {
    std::string escaped;
    // Reserve enough space for each character to be escaped
    escaped.reserve(originalString.GetLength() * 2); 
    for (int iOriginal = 0; iOriginal < originalString.GetLength(); ++iOriginal) {
        if (originalString[iOriginal] == charFind)
            escaped += charInsert;
        escaped += originalString[iOriginal];
    }
    return CString(escaped.c_str());
}
Eclipse
Bravo! Your code is more clear and idiomatic than the given code, and considerably more efficient.However, you've got a mistake there. Instead of "escaped += charFind;", it should be "escaped += originalString[iOriginal]".
Sol
+3  A: 

My gut reaction is: WTF. Initially by how the code is formatted (there are lots of things I don't like about the formatting) and then by examination of what the code is actually doing.

There's a serious issue with this developer's understanding of object copying in C++. The example is a WTF in itself (if the developer of the function really used his/her own function like this):

// Example call
strColHeader = insert_escape(strColHeader, ',', '\\'); //Get rid of the commas and make it an escape character

CString insert_escape ( CString originalString, char charFind, char charInsert )
  1. Pass a copy of strColHeader as originalString (notice that there's no &)
  2. The function modified this copy (fine)
  3. The function returns a copy of the copy, which in turn replaces the original strColHeader. The compiler will probably optimize this out to a single copy but still, passing around object copies like this doesn't work for C++. One should know about references.

A more seasoned developer would have designed this function as:

void insert_escape(CString &originalString, char charFind, char charInsert)

or:

CString insert_escape(const CString &originalString, char charFind, char charInsert)

(And probably would have named the parameters a bit differently)

And as many have pointed out, the sane thing the developer could have done was to check the API documentation to see if CString already had a Replace method...

Ates Goral
Passing a copy, modifying it and returning that is actually completely acceptable (idiomatic, even). Your const-ref signature has no advantage over it when using the same implementation internally!
Konrad Rudolph
when you're dealing with C++, it's likely that you've picked it over a higher level language for performance reasons. For instance, if you're writing server software with C++, every copy operation that you avoid counts (think CPU cycles, memory fragmentation etc.)
Ates Goral
true.. imagine the CString is a 100Mb csv file that needs mangling. The 2 extra string copies wouldn't be good then.
gbjbaanb
And in the original function, you need to make a copy of all the remaining data for every single comma in the original string.
Eclipse
+5  A: 

The mistakes have already been mentioned. But they strike me as the sort of problems anyone might have with quickly dashed-off code which has not been properly tested, particularly if they are not familiar with CString.

I'd worry more about the stylistic things, as they suggest someone who is not comfortable with C++. The use of the bool continueLoop is just plain poor C++. It represents a third of the function's code that could be eliminated by the use of a simple if...break construct, making the code easier to follow in the process.

Also, the variable name "originalString" is very misleading. Because they pass it by value, it's not the original string, it's a copy of it! Then they modify the string anyway, so it no longer is the same object or the same string of text as the original. This double lie suggests muddled thought patterns.

Sol
+2  A: 

I won't provide an alternative code, as it would only add to the code already provided.

But, my gut feeling is that there's something wrong with the code.

To prove it, I will enumerate some points in the original function that shows its developer was not an experienced C++ developer, points that you should investigate if you need a clean implementation:

  • copy : parameters are passed as copy instead of const-reference. This is a big NO NO in C++ when considering objects.
  • bug I guess there is an error in the "if(occurenceInd>0)" part. By reading CString's doc on MSDN, the CString::Find method returns -1, and not 0 when the find failed. This code tells me if a comma was the first character, it would not be escaped, which is probably not the point of the function
  • unneeded variable : "continueLoop" is not needed. Replacing the code "continueLoop = false" by "continue" and the line "while(continueLoop)" by "while(true)" is enough. Note that, continuing this reasoning enable the coder to change the functions internal (replacing a do...while by a simple while)
  • changing return type : Probably picking at details, but I would offer an alternative function which, instead of returning the result string, would accept is as a reference (one less copy on return), the original function being inlined and calling the alternative.
  • adding const whenever possible again, picking at detail: the two "char" parameters should be const, if only to avoid modifying them by accident.
  • possible multiple reallocation the function relies on potential multiple reallocations of the CString data. Josh's solution of using std::string's reserve is a good one.
  • using fully CString API : But unlike Josh, because you seem to use CString, I would avoid the std::string and use CString::GetBufferSetLength and CString::ReleaseBuffer, which enable me to have the same code, with one less object allocation.
  • Mysterious Insert method? it's me, or there is no CString::Insert ??? (see http://msdn.microsoft.com/en-us/library/aa252634(VS.60).aspx). In fact, I even failed to find CString in the same MSDN for Visual C++ 2008 and 2005... This could be because I should really go to sleep, but still, I guess this is worth investigating
paercebal
I agree with your points, but there is an error in the "unneeded variable" explanation. "continueLoop = false" should be replaced with "break" not "continue".
A. Levy
+1  A: 

Is this consultant being paid by the line of code? Several folks have pointed out that the CString class already provides this functionality, so even if you're not a programmer, you know:

  • The function is unnecessary. It adds to the complexity, size, and possibly the execution time of the program.
  • The CString function probably works and is probably efficient; this one may or may not be.
  • The CString function is documented, and is therefore supportable.
  • The consultant is either unfamiliar with the standard CString function or thought he/she could do better by writing a new one.
    • One might conclude that the consultant is unfamiliar with other standard features and best practices.
    • Choosing to write new code for a basic feature, without considering that a standard version may exist, is an accepted bad practice.

And perhaps the biggest, reddest flag of all: your instincts prodded you to get opinions from the StackOverflow community.

Trust your instincts.

Adam Liss