Lets Take a Look at Some Code

Let’s Take a Look at Some Code

In this section, we show the prototype source code that the ESRI team wrote to implement the “Generate Hotel Map for AOI” use case. It’s important to keep in mind that this is real-life code—not written for a dusty textbook showing a fictitious, contrived example.

As mentioned earlier, while busy learning how the new server software worked, the team skipped the detailed class diagram (the static model), making do instead with the domain model and the other up-front design diagrams created so far. This is a useful example of the trade-offs involved in deciding whether to stray from the process path (especially when up against a tight deadline, which is the point at which such dilemmas usually occur).

Luckily, the team had done enough up-front design on this project that skipping one design step didn’t cause any major issues. There’s always a judgment call involved: do you do it “by the book” or skip a step to save time and take the risk that it might store up trouble for later? In this case, the team skipped the class diagram (because this release is just a prototype) and as a result needed to do some refactoring of the code to get it ready for the next release. (We show some of these refactorings in this section and in Chapter 7.)

The existence of a comprehensive design spec allows us to easily determine what code will benefit from refactoring and what code should simply be left as is. When programmers begin coding, particularly in the early stages of product development, the code can be somewhat less than perfect. Modeling the design up-front goes a long way toward improving the initial state of the code, especially when the design process is followed stringently. But there’s often still room for improvement. Later we’ll show how to bring the code and the design documentation back in sync with each other.

Anyway, back to the source code. We don’t show the entire source code for each class here—just the parts relevant to the “Generate Hotel Map for AOI” sequence diagram in Figure 6-7). This should give you a flavor of the extent to which the sequence diagrams drive the code-level design.

image from book
ARCGIS SERVER WEB CONTROLS

It’s worth giving a little background information here about the framework that the mapplet was developed on. As we described in Chapter 5, the mapplet is a web-based application developed with ArcGIS Server, using Microsoft’s .NET Framework.ArcGIS Server includes an Application Developer Framework (ADF) built on top of .NET, which the mapplet team used to integrate GIS functionality into the web application.

When you create a new project in Visual Studio .NET, you’re presented with a set of templates that you can use as a starting point to create an application. Visual Studio .NET is highly extensible—for example, when you install the ArcGIS Server ADF, it adds a folder to Visual Studio containing an array of templates and custom web controls. Some of these predefined templates were used directly in the mapplet application, for example:

  • Map Viewer template: Offers basic map navigation and display

  • Search template: Finds features by attributes

  • Geocoding template: Locates places by address

The Map Viewer template provides basic map display capabilities. It consists of a main map, an overview map, a table of contents, a North arrow, and a scale bar.The template also contains a toolbar with built-in tools for panning and zooming. For any map-centric application, the Map Viewer template offers a good starting point. (Alternatively, you can use the web controls directly to create your own specialized application in a style that conforms to your existing website.)

The following image shows a conceptual object model diagram for the .NET ADF web controls.

image from book

image from book

Source Code for the “Generate Hotel Map for AOI” Use Case

This use case can be considered to be a “system use case” because the user doesn’t directly trigger it. Instead, it is triggered by other use cases. For example, if the user opts to search for a hotel by zip code, or zoom in to the current map, then the AOI would change and the map would need to be updated (hence the name “Generate Hotel Map for AOI”).

We’ll trace through the source code for the search by zip code example.

Search by Zip Code

The following code declares the ASP.NET Web Forms UI components for searching by zip code:

 protected System.Web.UI.WebControls.Panel pnlZipCode;  protected System.Web.UI.WebControls.Label lbZipCode;  protected System.Web.UI.WebControls.TextBox txtZipCode;  protected System.Web.UI.WebControls.Button btnSearchZipCode; 

Figure 6-10 shows how those components look on the web page (see the four page elements in the middle of Figure 6-10: the Zip Code label, the text field, the Go button, and the shaded area that contains them).

image from book
Figure 6-10: The four controls as they appear on the web page

When the Go button is clicked, an HTTP request needs to be sent to the web server, which is then picked up by a server-side event handler. The event handler is set up using this line of code (when the component is initialized):

 this.btnSearchZipCode.Click += new System.EventHandler(this.btnSearchZipCode_Click); 

When the request comes in, the zip code is contained in an HTTP request parameter called ZIP. The request handler simply copies this into a local variable called zip, which is then checked for in a method called CalcNewExtent in the MapViewer object. Here’s a small part of this method:

 private bool CalcNewExtent (out double XMin,                                               out double YMin,                                               out double XMax,                                               out double YMax,                                               out string ErrMsg)  {      // . . . .      #region Check Zip Code          if (zip.Length > 0)          {              try              {                  GetZipCodeExtent (zip, out XMin, out YMin, out XMax, out YMax);              }              catch              {                  ErrMsg = "Cannot find the zip code.";                  return false;              }              return true;          }      #endregion} 

The preceding code calls the GetZipCodeExtent method with the zip code, and gets in return the values for XMin, YMin, XMax, and YMax—in other words, the new AOI (the area of the map that needs to be displayed to the user).

The GetZipCodeExtent method in turn calls the actual Map Server to retrieve an AOI that includes the zip code. Here is the method in full:

 private IEnvelope GetZipCodeExtent (string zip, out  double XMin, out double YMin,  out double XMax, out double YMax)  {      XMin = 0;      YMin = 0;      XMax = 0;      YMax = 0;      int ZipLayerIndex = int.Parse (                    ConfigurationSettings.AppSettings ["ZipLayerIndex"]);      IServerContext sc = getSOM().CreateServerContext (                    ConfigurationSettings.AppSettings ["ServerObject"], "MapServer");      try      {          IMapServer ms = (IMapServer)sc.ServerObject;          String MapName = ms.DefaultMapName;          IMapServerObjects mso = (IMapServerObjects) ms;          IMap map = mso.get_Map(MapName);          IFeatureLayer fl = (IFeatureLayer) map.get_Layer (ZipLayerIndex);          IFeatureCursor fc = null;          IQueryFilter qf =                      (IQueryFilter) sc.CreateObject ("esriGeodatabase.QueryFilter");          qf.WhereClause = "ZIP = '" + zip + "'";          fc = fl.Search (qf, false);          if (fc == null)              return null;          IFeature f = fc.NextFeature ();          if (f == null)              return null;          XMin = f.Shape.Envelope.XMin;          XMax = f.Shape.Envelope.XMax;          YMin = f.Shape.Envelope.YMin;          YMax = f.Shape.Envelope.YMax;          return f.Shape.Envelope;      }      finally      {          sc.ReleaseContext ();      }  } 

Something that springs to mind about this source code so far is that it could probably do with a bit of refactoring. The code does its job, and (we’re proud to report) is stable and doesn’t seem to contain any bugs. But code quality isn’t just about the bug count for the current iteration. It’s also about whether the code can be maintained and the demands that it will place on future code that must coexist with it.

In the example code so far, we’ve seen two methods that take a series of numeric types (doubles) as an output parameter. These doubles (XMin, YMin, XMax, and YMax) are actually the left, top, right, and bottom coordinates for the AOI. As well as cluttering the interface, this approach is also error-prone, as the four doubles could easily be typed out of sequence (“Now should that have been ‘XMin, YMin, YMax, XMax,’ or was it ‘XMin, XMax, YMin, XMin’ oh, hang on!”).

image from book
DUPLICATE NAME POLICE

At this point, you might be wondering where the term “extent” has come from. We’ve seen “area of interest” (AOI) used quite a lot. As it turns out,“AOI” and “extent” are basically different names for the same thing. The term “extent” is used extensively in GIS applications, and ArcGIS Server also has an object called Extent.

Normally, the domain model helps to prevent this sort of duplicate naming. Having identified duplicate objects in the model, the team should choose one term or the other and stick with it. Occasionally, of course, a duplicate term slips through the cracks and ends up in the code. It’s important to eradicate these duplications as soon as they’re discovered.

In this instance (as we’ll see in the next chapter), the team eradicated “extent,” going instead with “AOI” in the early stages of the next release.

image from book

Our personal feeling about output parameters in C# is that they encourage programmers to pollute their code interfaces with additional parameters, when it’s much more likely that what’s needed is a new class to group the parameters together. In our example, an AOI class could be defined as follows:

 public class AOI  {      public double XMin = 0;      public double XMax = 0;      public double YMin = 0;      public double YMax = 0;      public AOI()      {      }      public AOI(IEnvelope envelope)      {          XMin = envelope.XMin;          YMin = envelope.YMin;          XMax = envelope.XMax;          YMax = envelope.YMax;      }      public double XMin      {          get          {              return XMin;          }          set          {              XMin = value;          }      }    // same again for XMax, YMin and YMax . . . .  } 

Note the optional constructor, which takes a parameter of type IEnvelope. IEnvelope is a class defined by the ESRI Map Server, similar to AOI.

Then the previous method signatures would be changed to

 private bool CalcNewExtent (out AOI aoi, out string ErrMsg) 

and

 private IEnvelope GetZipCodeExtent (string zip, out AOI aoi) 

That’s better! Now we have a nice, clean interface with the implementation details (coordinates, in this case) encapsulated away.

If we check back to the domain model shown in Chapter 5, we see that there was actually a class called AreaOfInterestViewer defined. Somewhere along the way, AOI as an independent entity sort of slipped through the cracks. In fact, AOI does get mentioned a lot in the robustness and sequence diagrams shown in this chapter, but (considering it’s a pretty obvious noun) it should also have been a “first-class” object on the sequence diagrams.

What the object model gives us is a blueprint to check back and validate that all the classes that are supposed to be there actually are there. If there’s anything missing or different, then we either update the object model to bring it in line with the code or update the code to bring it in line with the object model. In the example, it’s blindingly obvious that the code will benefit from the additional AOI class, so in this instance we update the code to bring it in line with the object model.

Another refactoring that can be made to the example code is to simplify the GetZipCodeExtent method. Currently the method does a lot of things, and the golden rule of program cohesion (well, okay, one of them!) is that each method should only do one thing.

While we’re at it, we could also rename some of the variables so that they’re more meaningful. Names like qf and f don’t stand up well on their own; you have to scan some more code to decipher what they mean. They’re also difficult to search for (try doing a text search for f!). Names like QueryFilter and Feature convey much more information.[8.]

We notice that in addition to its AOI output parameter, GetZipCodeExtent is returning an IEnvelope object (basically the equivalent of the AOI). We can eliminate this duplication by making AOI the return type and removing AOI from the parameter list.

It’s worth showing the GetZipCodeExtent method again in full, using the new AOI class, and refactored with improved naming and extracted methods:

 private AOI GetZipCodeExtent (string zip){      IServerContext context = getSOM().CreateServerContext(                  ConfigurationSettings.AppSettings ["ServerObject"], "MapServer");      try      {          IMap map = FindDefaultMap (context);          IFeatureLayer FeatureLayer = FindFeatureLayer();          IFeatureCursor FeatureCursor = FindFeatureCursor(zip, context);          if (FeatureCursor == null)              return null;            IFeature feature = FeatureCursor.NextFeature ();          if (feature == null)              return null;          return new AOI(feature.Shape.Envelope);      }      finally      {            context.ReleaseContext ();      }  }  private IMap FindDefaultMap (IServerContext context)  {      IMapServer server = (IMapServer) context.ServerObject;      IMapServerObjects mso = (IMapServerObjects) server;      return mso.get_Map(server.DefaultMapName );  }  private IFeatureLayer FindFeatureLayer()  {      int ZipLayerIndex = int.Parse (                                ConfigurationSettings.AppSettings ["ZipLayerIndex"]);      return (IFeatureLayer) map.get_Layer (ZipLayerIndex);  }  private IFeatureCursor FindFeatureCursor(string zip, IServerContext context)  {          IQueryFilter QueryFilter = (IQueryFilter) context.CreateObject                                                     ("esriGeodatabase.QueryFilter");          QueryFilter.WhereClause = "ZIP = '" + zip + "'";          return FeatureLayer.Search (queryFilter, false);  } 

In this version, notice how much easier it is to quickly read through the GetZipCodeExtent method. There are still some refactorings that could be done to this code, but it’s certainly a start.

Note 

Of course, when we’re refactoring, we also need a decent set of unit tests. We discuss how to combine Test-Driven Development (TDD) with ICONIX Process in Chapters 12.

The Class Diagram

Figure 6-11 shows the class diagram reverse-engineered from the C# source code. In Chapter 7, we’ll revisit the design and present a refactored version of this class diagram. It should be interesting to compare the two.

image from book
Figure 6-11: The class diagram reverse-engineered from the C# source code

[8.]We’re also dead set against the opposite affliction: variable names that are too long. Names like ServerLayerAppendedFollowingAdjustmentInt take ages to read (not to mention ages to type) and just annoy people. Avoid!



Agile Development with ICONIX Process. People, Process, and Pragmatism
Agile Development with ICONIX Process: People, Process, and Pragmatism
ISBN: 1590594649
EAN: 2147483647
Year: 2005
Pages: 97

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net