Horrible JDBC Code and What's Wrong With It
Ah, Efficiency: Thou art a goddess who deserves worship, adulation, sacrifice, but thou art also a goddess who gets said worship, adulation, and sacrifice in all the wrong ways. Here's some JDBC code validating a login, trying to be good code (I think!)... that gets it wrong in almost every way possible.
This is real code from a #java person. Thankfully, he was pasting it as an example of the dreck he has to work with... it's not his code!
public boolean validateLogin( String custno, String pincode )
throws Exception {
if( custno == null || pincode == null )
return false;
if( custno.length() == 0 || pincode.length() == 0 )
return false;
if( custno.matches(".*[^0-9].*")
|| pincode.matches(".*[^0-9].*") )
return false;
MyResultSet rs = sql.executeQueryTable(
"select custno from customers where custno = "
+ custno + " and pincode = " + pincode );
if( rs.next() ){
if( rs.next() )
throw new Exception( "More than one customer "
+ "found with custno " + custno
+ " and pincode " + pincode
+ ". Check databasekeys!" );
return true;
}
return false;
}
Now, like I said, this was pasted as real code, not "let me craft some horrible code together" example stuff.
So: let us count the ways...
The method throws an Exception. Note that it returns a true or false value, meaning "valid or invalid login" -- if an exception occurs, the login is apparently false! But it throws an Exception anyway... and it's a meaningless exception, forcing methods that call this one to handle generic exceptions.
pincodeis a String, not a real number. I don't know about you: when I see "pin code," I think of codes like "1234," not "mypassw0rd" -- yet they're using the loosest datatype they can. Bad, bad, bad.But wait! It gets worse! The code validates the input... to make sure that it's not null (why not just accept the NullPointerException?), to make sure it's not empty (more on this later), and then... to make sure it's numeric only. If they'd used the right datatypes in the first place, none of the validations would be necessary.
But wait! It gets even worser! Note the lack of brackets. Wasting code and CPU time is bad, according to whoever wrote this, but we must save the brackets! In my opinion, brackets - i.e., blocks - are worth using, even if it's a single statement the block contains. This prevents someone sticking a statement at the "end of the block" without realising it's not a block.
Whoever wrote this - and yes, I'm glad I don't know who you are, really - you wrote a method as defensively as you could, but you wrote it in a defensive way that leaves you wide open for bugs, especially bugs written by lazy maintenance coders. Do better. It'll save you from having to look at this code again later... and let's be real, this is a coding horror, nobody wants to look at it in the first place, much less look at it again.
On the other hand, if the code is maintained by someone else, maybe they'll clean it up...
SQL Injection alert? No, because they do validation of the data before building a SQL query with it... but seeing a statement built this way, with constant fields in it, should really bother anyone. Why "MyResultSet?" Is there some special facility that this requires that ordinary and canonical JDBC cannot handle?
No.
Now, it's not apparent in this code, but this queries a table... that has a primary key based on one of the fields in the query,
custno. The code gets the result set, iterates to the first row, and then... checks to see if there's ANOTHER ROW.Apparently, in whatever database this code anticipates using, the definition of "primary key" doesn't include the property of uniqueness, which makes it pretty much not a primary key.
Look: if you're doing a check for logins like this, fine. But real code should use a PreparedStatement; it should use the right data types; ... it should look something like this:
/** assume Connection conn is in scope
* for the method for clarity
*/
public boolean isValidLogin(int custno, int pincode) {
PreparedStatement ps=null;
ResultSet rs=null;
boolean isValid=false;
try {
ps=conn.prepareStatement(
"select custno from customers "+
"where custno=? and pincode=?");
ps.setInt(1, custno);
ps.setInt(2, pincode);
rs=ps.executeQuery();
isValid=rs.next();
} catch(SQLException e) {
/* log SQL error on an internal log */
} finally {
if(rs!=null) {
try { rs.close(); } catch(Exception e) {}
}
if(ps!=null) {
try { ps.close(); } catch(Exception e) {}
}
}
return isValid;
}
There's lots of code here, too, but the big difference is that all of it's meaningful in a Java context, instead of wasted CPU time. As it is, it's a terrible idea.
Re: Horrible JDBC Code and What's Wrong With It
"0789" != "789".
and I totally disagree with the Exception handling in your solution. If there is a SQLException it should be rethrown or encapsulate. Otherwise it would just be another ERROR entry in the log. The user will probably think he entered the wrong credential ... and try again instead of calling support directly .
} catch(SQLException e) {
/* log SQL error on an internal log */
Re: Horrible JDBC Code and What's Wrong With It
public boolean isValidLogin(int custno, int pincode) {
return 1 == getSimpleJdbcTemplate().queryForInt("select COUNT(*) from customers "+
"where custno=? and pincode=?", custno, pincode);
}
Maybe using COUNT(*) isn't the best idea, but this way seems simpler. I would create extend the JDBC template and add an exists(sql, params...) method.
Re: Horrible JDBC Code and What's Wrong With It
Re: Horrible JDBC Code and What's Wrong With It
Even if you don't want to use Spring (I can definitely see why one wouldn't), you can write your own JdbcTemplate analogue -- it may not be totally trivial, but it's not very complicated either and one that handles 80% of the functionality can probably be done in 200 lines of code (wild guess). Maybe you could even copy some of the code from Spring, I think the license allows that.
Re: Horrible JDBC Code and What's Wrong With It
You fail to catch any Exceptions outside SQLException. There should always be at least (and this includes examples) a standard Exception catch.
For example you may wish to catch runtime exceptions. If you failed to do this you could fail to close ps and rs and leave them unclosed.
Also I would null them as well after closing to let the GC know to collect them asap.
And there are probably many others but I cannot be bothered right now.
Re: Horrible JDBC Code and What's Wrong With It
Re: Horrible JDBC Code and What's Wrong With It
I'd say not all companies have numbers for customer id, therefore keeping it as a String still forces you to check it's not null/empty so that you don't waste a database connection. This could be true of the password as well, but we could never know what types are in the database anyways. Unless of course, a prepared statement was used.
I'd say the cardinal sin here is not using a PreparedStatement in order to spare the database and help with XSS or Sql injection.
Then the nested rs.next() is pretty funny really. Never seen it before.
Then the method should throw a runtime AuthenticationException, and a ServiceTemporarilyUnavailableException (after logging underlying exception) rather than a boolean value.
etc.
Re: Horrible JDBC Code and What's Wrong With It
Most of your points go straight at the problem definition, and not the code. The original code made obvious an assumption that all customer numbers were, well, numbers. Using a string when a number is mandated is pointless.
The situations where exceptions exist for failure are also problem-domain related; the original code returned true or false, where false was apparently a catchall for "all failure conditions, including invalid authentication."
I'd agree with you for the most part: throw an unchecked exception in the case of a service failure, and return a "no user" value for a non-matching condition. <code>boolean</code> is a little too simple for this.