ctodx

This Blog

News

Favorite Posts

Archives

July 2009 - Posts

  • Quick refactoring with Refactor! Pro

    A moment ago, I was writing some code that configured a chart and I thought it would be good to quickly post what I was doing, not because it was especially difficult XtraCharts code, but it illustrated a refactoring technique that not many people know.

    The code I'd written added two horizontal constant lines to a bar chart. This was the starting point:

        // set up the constant lines
        ConstantLine line = new ConstantLine();
        line.ShowBehind = true;
        line.AxisValue = 1000000;
        line.ShowInLegend = false;
        line.Color = Color.DarkSeaGreen;
        line.LineStyle.DashStyle = DashStyle.Dot;
        diagram.AxisY.ConstantLines.Add(line);
    
        line = new ConstantLine();
        line.ShowBehind = true;
        line.AxisValue = 1500000;
        line.ShowInLegend = false;
        line.Color = Color.DarkSeaGreen;
        line.LineStyle.DashStyle = DashStyle.Dot;
        diagram.AxisY.ConstantLines.Add(line);

    I'm sure, like me, you're shuddering at the repetition. No sweat, I deliberately decided to code it like this, prior to removing the redundancy, so that I could make sure that (a) it worked, and (b) that I'd configured the lines the way I wanted. As it happened, after that bit of coding, the only difference between the two blocks of code was the value, but I could have decided on, say, different colors for the lines as well, so it was worth doing it this way.

    I highlighted the first block of code, minus the line that added the line to the axis' collection of lines, hit the Refactor key (the back tick) and extracted a method. The extracted method looked like this:

      private static ConstantLine GetLine() {
        ConstantLine line = new ConstantLine();
        line.ShowBehind = true;
        line.AxisValue = 1000000;
        line.ShowInLegend = false;
        line.Color = Color.DarkSeaGreen;
        line.LineStyle.DashStyle = DashStyle.Dot;
        return line;
      }

    I'm fine with the automatically-generated name of the method (and that it's marked static), but I now need to add a parameter to pass in the axis value (here it's 1000000). This is a two step process.

    First declare the constant as a local: highlight the value and select the "Declare Local" refactoring. I called the new variable axisValue:

      private static ConstantLine GetLine() {
        ConstantLine line = new ConstantLine();
        line.ShowBehind = true;
        int axisValue = 1000000;
        line.AxisValue = axisValue;
        line.ShowInLegend = false;
        line.Color = Color.DarkSeaGreen;
        line.LineStyle.DashStyle = DashStyle.Dot;
        return line;
      }

    Second, promote that local variable to a parameter. No need to highlight the variable name, just hit the Refactor key while the cursor is in the identifier.

      private static ConstantLine GetLine(int axisValue) {
        ConstantLine line = new ConstantLine();
        line.ShowBehind = true;
        line.AxisValue = axisValue;
        line.ShowInLegend = false;
        line.Color = Color.DarkSeaGreen;
        line.LineStyle.DashStyle = DashStyle.Dot;
        return line;
      }

    Just what I wanted. I then hit Esc to gather the marker left by the Extract Method refactoring to see this:

        // set up the constant lines
        ConstantLine line = GetLine(1000000);
        diagram.AxisY.ConstantLines.Add(line);
    
        line = new ConstantLine();
        line.ShowBehind = true;
        line.AxisValue = 1500000;
        line.ShowInLegend = false;
        line.Color = Color.DarkSeaGreen;
        line.LineStyle.DashStyle = DashStyle.Dot;
        diagram.AxisY.ConstantLines.Add(line);

    I can now inline that assignment since there's no need to assign the result of GetLine to a variable, and then pass the variable to the Add method. Note the variable is not removed by the refactoring since it's used lower down.

        // set up the constant lines
        ConstantLine line = GetLine(1000000);
        diagram.AxisY.ConstantLines.Add(GetLine(1000000));
    
        line = new ConstantLine();
        line.ShowBehind = true;
        line.AxisValue = 1500000;
        line.ShowInLegend = false;
        line.Color = Color.DarkSeaGreen;
        line.LineStyle.DashStyle = DashStyle.Dot;
        diagram.AxisY.ConstantLines.Add(line);

    Now I can dupe the code that adds the new line from a call to GetLine, change the parameter to 1500000, and delete the other unnecessary lines.

        // set up the constant lines
        diagram.AxisY.ConstantLines.Add(GetLine(1000000));
        diagram.AxisY.ConstantLines.Add(GetLine(1500000));

    Done. Of course, it would be nice if Refactor! Pro noticed the duplicated block during the Extract Method refactoring and fix it up too. We're working on that.

  • DevExpress Newsletter 7: Message from the CTO

    My Message from the CTO in our seventh newsletter:

    Information overload

    We live in a world of information. It comes at us from all angles, via lots of different ways, and from many people. We get information from books, newspapers, blogs, Twitter, and so on. If we're not to drown, we quickly evolve ways to snatch relevant tidbits from the fire hose of information that blasts past us. We may use filters to only see the items that interest us, we may search for relevant data, we may apply some kind of tool that provide a different visualization of the information, we may look for aggregators that do the filtering and the checking for relevancy for us, and so on.

    Your users are the same way, except that the fire hose of information they have is being pumped by software you wrote.

    You should be providing ways for them to pick out what's relevant from what isn't. Provide search boxes. Provide the ability for them to filter the information, or to group and sort it. Provide "aggregators" or dashboards that do this for them, with the ability to drill down if they want to see the fine print. Provide different visualizations, such as charts and the like, to help them understand what's going on.

    Even simpler: categorize the way you display information so that you are showing what's relevant in a higher contrast than that which is not as relevant (our Mark Miller has a series of blog posts about this kind of relevancy visualization).

    So, think of your users. How can you provide tools to help them filter out the relevant streams of information from their fire hose?

    It's amazing to look at software these days versus that from ten years ago, say. Then you had a Help button if you were lucky, but these days the ubiquitous Search box provides a better user experience. What's next I wonder?

  • More on our new functional test framework: scripting

    Yesterday, I introduced EasyTest for XAF, our new functional testing framework for applications built using our application framework. Today, I'd like to reveal a few more details.

    A bit of history

    Why produce a test framework in the first place? Let's look at some background. There are, I suppose, two main classes of testing tool for .NET applications:

    1. There's the dig-in-deep tool. This hooks into the application’s message loop and records/plays back particular messages. This approach was quite popular in the good old days. As you perform some actions on the UI, the testing tool records the actions as a set of mouse moves and clicks, and key presses; a set of messages, if you will. The resulting test script is completely unreadable, since in essence, it’s just a large list of screen coordinates and message ids and parameters. You can’t really guess what’s going on by looking at the script. You can’t understand what functionality is being tested. When such a script fails because the developer rearranges the controls on the form (or for any of a long list of reasons), it usually has to be rerecorded again.

    2. There's the keyword-driven tool. This tool doesn’t record screen coordinates or key presses, but instead runs a test script that represents a set of actions performed on particular interface elements, such as buttons and text fields and the like. These scripts are much more readable and you don't usually need much experience to write them. Plus, the tests don’t break when you rearrange the controls on a form since the tool recognizes the names of the controls, rather than their locations. On the downside, though, a keyword-driven testing tool has to “know” about particular controls (properties, events, and even methods) in order to support them. If the tool doesn’t support a given control, say one from a third party vendor, you need an adapter. If the adapter doesn't exist, you'll only be able to use the control at its most basic, "ancestral" level.

    We felt that neither of these approaches were good enough for testing XAF applications. For a start, XAF is designed to produce multi-platform applications, either a WinForms application or an ASP.NET application or both. The set of supported platforms is likely to be extended in the future to WPF and Silverlight applications. Even now, with only these two current platforms, it's likely you’re going to need two different testing tools, which, of course, means writing or recording different scripts.

    The other thing to appreciate is that XAF already has a set of high level abstractions, such as Views or List Editors. There's no point in testing the abstractions, which is what another test tool would be forced to do. If a testing framework existed that knew about and that would take these abstractions into account, the test scripting commands would become much more compact and concise.

    And from those considerations, EasyTest for XAF was born.

    EasyTest Scripts

    The language that EasyTest scripts use is declarative. You tell EasyTest what needs to be achieved, and the system interprets these wishes into actions for the particular type of application that's being tested. The test scripts are platform-independent.

    As already mentioned, we wanted to use XAF high-level abstractions, but at the same time, we wanted end-users to be able to write functional test scripts. Since an end-user most likely doesn’t know about the various XAF abstractions, the script language shouldn’t use them. So, the scripting commands are named differently, which is why Property Editors ended up being Fields, List Editors got renamed to Tables, and the objects represented by List Editors to Records.

    In order to understand the commands used, let's look at an example EasyTest script:

    ;#DropDB MySolution
    
    #Application MySolutionWin
    #Application MySolutionWeb
    
    *Action Navigation(User)
    
    *SelectRecord
     Full Name = ''
     
    *FillForm
     Full Name = Test User
     
    *Action Cancel
    
    #IfDef MySolutionWin
    *HandleDialog
     Respond = Yes
    #EndIf
    
    !CheckFieldValues
     Full Name = 'Test User'

    The first thing to notice is that there are three types of statements: the directives, which are prefixed with the # sign, the commands, which are prefixed with an asterisk, and the property (parameter) values, which are the rest. You'll note also that the first statement is prefixed with a semicolon, which is the indicator that the rest of the statement is a comment.

    The directives don't perform any actions. They just provide information about the application being tested, the database to be used, any timeouts that should be set, and so on.

    The commands of course perform the actions that the test is supposed to execute. You declare what should happen on a "macro" level and EasyTest will determine the best way to implement that requirement according to the type of application being tested.

    Let's go through the script line by line:

    ;#DropDB MySolution

    Firstly, it's a comment (because it's preceded with a semicolon), but if it weren't it would be a directive to delete the application’s database, so that a newly created clean database can be used for testing purposes. Since this operation could potentially destroy a lot of valuable data, the command has been temporarily commented out. The MySolution parameter specifies a database to be deleted; this is defined in the EasyTest configuration file.

    Usually an XAF solution has a single EasyTest configuration file to serve as the central store for configuration information. It essentially allows you to use the same set of scripts to test different applications by supplying different configuration files. Hence, whenever you need to change any settings, you only need to do it in one place.

    #Application MySolutionWin
    #Application MySolutionWeb

    These commands specify the applications to be tested. Again, the MySolutionWin and MySolutionWeb parameters point to the EasyTest configuration file.

    *Action Navigation(User) 

    This command performs an action. In this instance, it’s the Navigation action, and this is used to navigate to the View specified by the User navigation item.

    *SelectRecord
     Full Name = ''

    This command works with a List Editor. The command selects the object that has the FullName property set to an empty value, and invokes the Detail View for that selected object.

    *FillForm
     Full Name = Test User

    This command fills the Detail View’s Property Editors with the specified values.

    *Action Cancel

    This command performs the Cancel Action.

    #IfDef MySolutionWin
    *HandleDialog
     Respond = Yes
    #EndIf

    This block of commands are only executed for the Windows Forms application because of the IfDef directive. The HandleDialog command stipulates that the test framework must handle the confirmation dialog. The reason it's inside the IfDef block is because XAF ASP.NET Web applications don’t have this dialog.

    !CheckFieldValues
    Full Name = 'Test User'

    This command checks that certain field values conform to the conditions specified by secondary parameters. The command is prefixed with the exclamation point instead of the asterisk to specify that the command is expected to fail. So, in this instance, the command ensures that the FullName property doesn’t equal ‘Test User’.

    Running the Tests

    As stated in the previous post, you can run EasyTest functional tests either from Visual Studio (which is in essence a developer scenario), or from a standalone test runner.

    TestExecutor

    The test runner is a command-line tool that can execute EasyTest scripts. The tool even outputs logs in XML files, and, if a test didn’t pass, in addition to writing to the log file, the test runner takes a screenshot, capturing the state of the application’s UI at the time of failure.

  • Very sneaky peek: Functional test framework for eXpressApp Framework (XAF)

    Some new functionality just managed to get into DXperience v2009 vol.2 under the code freeze wire: functional testing in XAF. In fact it was so close and so new, we haven't got that much documentation for it yet. So, the decision was between, eh, tough, nice try guys, but don't tell anyone and keep it out until v2009.3, or, keep it in and document as we go. Guess which path we're taking? Smile

    Welcome to EasyTest for XAF: a test framework that does functional testing for XAF applications.

    The big idea here was to create a very simple and easy-to-use framework for testing the multi-platform XAF applications you write. We aimed for functional testing, that is, testing of the overall functionality of the application rather than the unit testing of single methods and the like. Furthermore, since XAF can create WinForms and ASP.NET applications, our goal was to be able to test both platforms with one tool rather than have a separate "WinForms" testing tool and an "ASP.NET" testing tool. Another goal was to allow the QA team to create tests, rather than the developers, and to support this you can run EasyTest tests standalone, rather than in Visual Studio (which is what I'll talk about here).

    With some bribes of pizza and beer, we got some help from the CodeRush team to fully integrate EasyTest into Visual Studio so that you can do things like run (and stop) tests and even step through XAF tasks and actions.

    Stepping

    (Note: the pane on the left shows the test script being executed. We'll talk about the grammar of the script commands in a later post.)

    Not only that, but EasyTest also supports breakpoints:

    Breakpooints

    While an application is being tested, EasyTest provides the actions that you've already specified and the application runs on its own.

    auto-run

    At the moment, the logging facilities are pretty simple too: test messages and warnings are posted to the Output window in Visual Studio:

    Test Output

    Although I stated above that we didn't have much documentation yet, we are certainly in the process of writing it for the release. For such a feature though, it makes sense to continue to blog about it and explain its evolution, why we did it, how simple it is to use and run. So, please stay tuned as we explain more about functional testing in XAF, about test scripts, and about the standalone testing app.

  • DevExpress Newsletter 6: Message from the CTO

    My Message from the CTO in our sixth newsletter:

    The metric system for code

    If you've been developing for any length of time, you'll have come across any number of studies that correlate "bad things" against some form of software metric. The first book to really summarize these studies (although it's now getting a little long in the tooth) is Code Complete by Steve McConnell, but there have been others since that address code quality.

    So we have the correlation between lines of code in a method and the "maintainability" of the code, or the Cyclomatic Complexity of a method and its testability, or the coverage percentage for a method and its likelihood to contain bugs, or the coupling/cohesion of a set of modules and its flexibility when the demands for enhancement arrive.

    In other words, for some metric, there's some benefit to reducing (or increasing) its value. For some set of metrics, the problem becomes a little harder since you're increasing the number of measurement dimensions you can alter and therefore the number of resulting behaviors of the code. It starts to look like a linear programming problem.

    However, there is one metric to rule them all: time. At some point, you have to ship a product or an application. You do not have the luxury of infinite time to tweak your other metrics to solve the quality code problem. You have to ship the best software you can in the time allotted, and if that means ignoring your favorite code metric then so be it.

    I suppose what I'm arguing here is by all means write your code in the first place to follow some of your favorite metrics (one of mine is Cyclomatic Complexity max 10 or less, usually 5 or less, and it's easy to check with CodeRush), but don't get all hot under the collar that some code misses the mark. Time is usually of the essence, so be flexible about the rest, so long as you're not abandoning all your principles.

  • Improving the WPF DataGrid performance

    Over the past six months, the DXGrid team has been making progress improving our WPF data grid in two orthogonal directions. First they've been adding extra functionality you can read about here; things like printing support (including printing to PDF, HTML, XLS(X), etc, etc), editors, data validation, new item row, and so on, and second, they've been improving its performance.

    Don't get me wrong, the performance was great to begin with. We've always gone after performance when scrolling, filtering, sorting, grouping and the like, and, like our other data grids for WinForms, ASP.NET and Silverlight, we've certainly concentrated on processing large data sources quickly using server mode. But there was one scenario we weren't happy with: the initial load time. That is, the time taken from starting to display a form containing a grid to the point where it's usable.

    I thought it would be instructive to anyone creating a WPF application to discuss what we found and what we decided to do about it. The conclusions we reached have already affected (and will affect) our other controls and you may learn something from this investigation yourselves in your own work.

    The team did a couple of spikes with profiler in hand. (Yes, it does bear pointing out explicitly: you can't decide anything about performance or know where such issues exist unless you use a profiler. There are a couple of very good ones on the market, so go buy one now if you're interested in improving performance.) The spikes gave us these two issues:

    • we had a large visual element tree for each row and cell
    • we had a large number of data bindings happening for each cell

    The first was more due to our desire to have a very flexible and theme-able data grid, where you, the developer/designer, could manipulate appearance and effects down to the smallest detail using XAML. Once the visual tree was created (which took time), it would be reused for any further manipulations with the grid and was very fast since everything was already present. The conclusion was we should reduce the size of the visual tree to improve the initial load time.

    Here's some statistics. Between the v2009.1 beta (which is all anyone has at the moment) and the v2009.2 release (coming soon), the number of visual tree elements for a text cell has been reduced from 11 to 4, and the number of visual elements for a row (excluding the cells), down from 38 to 32 (and we're dithering about removing another couple). What does that mean in terms of time? Well for a grid showing 30 active rows with 12 columns, the team have halved the initial load time. For 5 columns, it's about 2/3 of the original time.

    The benefits of these optimization efforts are three-fold: first, of course, the initial load is much faster. Second, the changes also improve the speed of operations like resizing or moving columns (the visual tree needs fixing up in those scenarios too), or of expanding/collapsing groups (again, the visual tree needs fixing). In these scenarios, the time taken to fix up the visual tree was originally hidden to a certain extent by the fetching of data.

    Third, despite the reduction in visual elements, we've still managed to maintain the flexibility of the tree with regard to applying themes. We felt that the ability to theme our grid was of extreme importance to the designers and developers using our WPF data grid: they should be able to easily change the appearance using XAML because, well, that's what you do with WPF. We did consider removing more visual elements and instead hard-coding the visual appearance (some work was done on this to prove it could have improved performance further), but we strongly believe that in so doing we'd have tossed out the appearance customization baby with the visual element bathwater. So, we stopped where we did because we thought that we had the right tension between flexibility and performance.

    The second performance issue above was due to some inefficient programming, more than anything else. So, in order to improve things, all we had to do was improve the code that bound data, or make sure it only happened once.

    So, in conclusion, when you are writing your WPF application, pay special attention to your visual tree. Just because creating a single visual element is fast, don't forget that it takes time to create a lot of them and it's all too easy to overload your visual element tree using the designer. So make sure that your own initial load times are kept in check.

  • DevExpress Newsletter 5: Message from the CTO

    Here's the Message from the CTO from the fifth DevExpress Newsletter:

    Being first or being best...

    It's a problem to be sure. You're working on a software solution for some new problem space. You know your competitors are working on similar solutions. What do you do: produce the best solution you possibly can, or release something quickly, but not quite as high quality or not quite as full-featured, just to be first?

    On the one hand, you're First. People will buy and then perhaps get "locked in" to your solution. You have the opportunity to do some of that dreaded marketing, but you also have the problem of persuading people that the problem space is worth solving.

    On the other hand, you have the opportunity to see what the first guy is doing, and how it falls short. You can modify your development to target the shortcomings. Maybe it's performance, maybe it's ease-of-use, maybe it's data import/export, or reliability or stability. No matter what, you have the ability to be more measured in your release. It's not guaranteed that you'll be able to persuade customers to buy yours or switch, but you'll have a better chance should the problem space be worth solving.

    Over the years DevExpress have been in the former camp and sometimes in the latter. With our WPF controls, we're firmly in the latter: we wanted to understand what people wanted (and whether the market was there) and we're almost ready to issue our first release. So expect better performance, improved ease-of-use, a more intuitive API, and all the other good things.

    Next time, maybe we'll be first, and best...

    It's the dichotomy we all face when writing software for sale. I'm reminded of it with the iPhone App Store, which is a place you can see this First vs. Best natural selection going on all the time. So long as the stuff you want to buy isn't "Duke Nukem Forever"...

LIVE CHAT

Chat is one of the many ways you can contact members of the DevExpress Team.
We are available Monday-Friday between 7:30am and 4:30pm Pacific Time.

If you need additional product information, write to us at info@devexpress.com or call us at +1 (818) 844-3383

FOLLOW US

DevExpress engineers feature-complete Presentation Controls, IDE Productivity Tools, Business Application Frameworks, and Reporting Systems for Visual Studio, along with high-performance HTML JS Mobile Frameworks for developers targeting iOS, Android and Windows Phone. Whether using WPF, ASP.NET, WinForms, HTML5 or Windows 10, DevExpress tools help you build and deliver your best in the shortest time possible.

Copyright © 1998-2017 Developer Express Inc.
All trademarks or registered trademarks are property of their respective owners