I ‘ve been trying to come up with things that lead to a series of follows up stuff. This way I don’t have to think too hard about coming up with subjects 🙂 and it makes writing them easier since I don’t have to spend so much time in general on them. And now from:
http://www.ayende.com/Blog/archive/2008/08/27/What-I-am-working-on.aspx
Oren is his name and he is one of the bloggers I follow pretty close. He’s been doing some pretty incredible stuff with a product he helped write called “Rhino Mocks” that allows you to create mock objects for testing and to set test conditions on those objects (ok, there’s a lot more but that scratches the surface) and in general he’s a pretty incredible coder.
Well, recently he posted some code that seems essentially how not to develop software with so many bad coding standards it would keep me busy for awhile. Take a look at the below code:
{
private string conStr;
private DataSet rates;
public TaxCalculator(string conStr)
{
this.conStr = conStr;
using (SqlConnection con = new SqlConnection(conStr))
{
con.Open();
using (SqlCommand cmd = new SqlCommand(“SELECT * FROM tblTxRtes“, con))
{
rates = new DataSet();
new SqlDataAdapter(cmd).Fill(rates);
Log.Write(“Read ” + rates.Tables[0].Rows.Count +
“ rates from database“);
if (rates.Tables[0].Rows.Count == 0)
{
MailMessage msg = new MailMessage(“important@legacy.org“, “joe@legacy.com“);
msg.Subject = “NO RATES IN DATABASE!!!!!“;
msg.Priority = MailPriority.High;
new SmtpClient(“mail.legacy.com“, 9089).Send(msg);
Log.Write(“No rates for taxes found in ” + conStr);
throw new ApplicationException(“No rates, Joe forgot to load the rates AGAIN!“);
}
}
}
}
public bool Process(XmlDocument transaction)
{
try
{
Hashtable tx2tot = new Hashtable();
foreach (XmlNode o in transaction.FirstChild.ChildNodes)
{
restart:
if (o.Attributes[“type“].Value == “2“)
{
Log.Write(“Type two transaction processing“);
decimal total = decimal.Parse(o.Attributes[“tot“].Value);
XmlAttribute attribute = transaction.CreateAttribute(“tax“);
decimal r = -1;
foreach (DataRow dataRow in rates.Tables[0].Rows)
{
if ((string)dataRow[2] == o.SelectSingleNode(“//cust-details/state“).Value)
{
r = decimal.Parse(dataRow[2].ToString());
}
}
Log.Write(“Rate calculated and is: ” + r);
o.Attributes.Append(attribute);
if (r == -1)
{
MailMessage msg = new MailMessage(“important@legacy.org“, “joe@legacy.com“);
msg.Subject = “NO RATES FOR ” + o.SelectSingleNode(“//cust-details/state“).Value + “ TRANSACTION !!!!ABORTED!!!!“;
msg.Priority = MailPriority.High;
new SmtpClient(“mail.legacy.com“, 9089).Send(msg);
Log.Write(“No rate for transaction in tranasction state“);
throw new ApplicationException(“No rates, Joe forgot to load the rates AGAIN!“);
}
tx2tot.Add(o.Attributes[“id“], total * r);
attribute.Value = (total * r).ToString();
}
else if (o.Attributes[“type“].Value == “1“)
{
//2006-05-02 just need to do the calc
decimal total = 0;
foreach (XmlNode i in o.ChildNodes)
{
total += ProductPriceByNode(i);
}
try
{
// 2007-02-19 not so simple, TX has different rule
if (o.SelectSingleNode(“//cust-details/state“).Value == “TX“)
{
total *= (decimal)1.02;
}
}
catch (NullReferenceException)
{
XmlElement element = transaction.CreateElement(“state“);
element.Value = “NJ“;
o.SelectSingleNode(“//cust-details“).AppendChild(element);
}
XmlAttribute attribute = transaction.CreateAttribute(“tax“);
decimal r = -1;
foreach (DataRow dataRow in rates.Tables[0].Rows)
{
if ((string)dataRow[2] == o.SelectSingleNode(“//cust-details/state“).Value)
{
r = decimal.Parse(dataRow[2].ToString());
}
}
if (r == -1)
{
MailMessage msg = new MailMessage(“important@legacy.org“, “joe@legacy.com“);
msg.Subject = “NO RATES FOR ” + o.SelectSingleNode(“//cust-details/state“).Value + “ TRANSACTION !!!!ABORTED!!!!“;
msg.Priority = MailPriority.High;
new SmtpClient(“mail.legacy.com“, 9089).Send(msg);
throw new ApplicationException(“No rates, Joe forgot to load the rates AGAIN!“);
}
attribute.Value = (total * r).ToString();
tx2tot.Add(o.Attributes[“id“], total * r);
o.Attributes.Append(attribute);
}
else if (o.Attributes[“type“].Value == “@“)
{
o.Attributes[“type“].Value = “2“;
goto restart;
// 2007-04-30 some bastard from northwind made a mistake and they have 3 months release cycle, so we have to
// fix this because they won’t until sep-07
}
else
{
throw new Exception(“UNKNOWN TX TYPE“);
}
}
SqlConnection con2 = new SqlConnection(conStr);
SqlCommand cmd2 = new SqlCommand();
cmd2.Connection = con2;
con2.Open();
foreach (DictionaryEntry d in tx2tot)
{
cmd2.CommandText = “usp_TrackTxNew“;
cmd2.Parameters.Add(“cid“, transaction.SelectSingleNode(“//cust-details/@id“).Value);
cmd2.Parameters.Add(“tx“, d.Key);
cmd2.Parameters.Add(“tot“, d.Value);
cmd2.ExecuteNonQuery();
}
con2.Close();
}
catch (Exception e)
{
if (e.Message == “UNKNOWN TX TYPE“)
{
return false;
}
throw e;
}
return true;
}
private decimal ProductPriceByNode(XmlNode item)
{
using (SqlConnection con = new SqlConnection(conStr))
{
con.Open();
using (SqlCommand cmd = new SqlCommand(“SELECT * FROM tblProducts WHERE pid=” + item.Attributes[“id“], con))
{
DataSet set = new DataSet();
new SqlDataAdapter(cmd).Fill(set);
return (decimal)set.Tables[0].Rows[0][4];
}
}
}
}
If this code doesn’t make you shudder I worry about you and suspect you shouldn’t be working as a computer programmer.
Over the course of the next few weeks, along with my posts on LINQ I want to spend some time analyzing what is wrong with this and help to try to define some of the established coding standards with .Net as well as coding in general.
Let’s start with the obvious. We all know I am anti-goto, with good reason. There is really no place in any modern high-level language for gotos. Look at this:
{
o.Attributes[“type“].Value = “2“;
goto restart;
// 2007-04-30 some bastard from northwind made a mistake and they have 3 months release cycle, so we have to
// fix this because they won’t until sep-07
}
Okay, if you look at the flow of the foreach this is in this else/if and breaks the flow of the code sending the execution back up to the top in mid-loop restarting everything over again.
But Brian, this had to be done, don’t you see the comments?
No my young padawan, it doesn’t. I’m not saying the fix for this shouldn’t be in place, what I’m saying is that the goto shouldn’t be there. If the original programmer thought about this a bit more he would have seen that if he simply put the statement at the beginning of the foreach as an if statement like:
{
o.Attributes[“type“].Value = “2“;
// 2007-04-30 some bastard from northwind made a mistake and they have 3 months release cycle, so we have to
// fix this because they won’t until sep-07
}
it would have had the same effect without needing the goto.
But Brian, by putting it later in the code then most of the time you will be able to try and capture the ifs earlier and then you won’t have to do a compare on every row for “@”.
So what? How do you know that every row won’t have the “@” for the value? In that case you actually hurt your code execution by not putting the if at the beginning of the foreach loop. Plain and simple gotos hurt code (see my earlier post on gotos with references on such). The studies have been done and there are no reason for them in a modern language.
On to the next point of the code:
Well, what’s wrong with this?
What if Joe quits the company? What if Important quits the company? These values should not be embedded in the code.
But Brian, Important is a not person you silly guy! It’s a mailing group.
Wow, sarcasm is not lost on you.
In modern .Net applications you have the Settings. This allows you to store values in an easy to use xml file. To get the values back out you don’t have to even do any special parsing, you just do (assuming you named your settings “MySettings”):
That’s right. When you add settings to your application you can then simply refer to the values in them directly as a property of the static Settings object in your project. Then, in the future if Joe or Important should no longer get the emails then you simply modify the xml file in the application directory (or use the Visual Studio settings editor) and change who it should go to. No need to recompile. It’s that simple. To add a settings, right click on the project name, select “Add New” and click on Settings file. Name it as you like and Visual Studio will open a settings editor where you can add these properties and values.
Brian, you’re a dumb ass. I tried this. My application spans multiple projects and the settings file is only scoped at the project level.
Fine, if you want to be that way be that way. Don’t use the settings features built in to the framework. But whatever you do don’t embed this stuff into the application code. If your smart and have been using a data access layer (DAL) like nhibernate or netTiers then getting and storing these values in the database should be trivial. While up front it’s a bit more of a pain, on the back end it is easier because then for each instance of your application running you don’t have to modify the settings file and deploy to each application. You just get the values out of the database.
Well, that’s it for now. Hopefully you all might be interested in this and if you aren’t you’ll still be getting the code. Doing a series like this is easy for me and allows me more time to do real work so there 🙂
Next time we’ll look at DALs and refactoring.
Later ‘yall,
Brian