Quality saves time and money!

In software projects quality saves time and money!.   

This principle is based on the observation that one of the most time consuming activity is rework. The rework arises from changes in requirements, change in design, or debugging. The single biggest activity of building software is debugging and correcting code that doesn’t work properly. Following my experience this activity account for about half of the time spent on development. Think at what you really do when you code, generally the time spent in writing production code is ridiculous compared to the time spent at debugging, fixing infrastructure issues, reading code, fixing bugs…

Therefore, the most obvious method to reduce our development cost is to improve the quality of the product and decrease the amount of time spent debugging and reworking our software. This analysis is confirmed by external field data. In a review of 50 development projects involving over 400 work-years of effort and almost 3 million lines of code, a study at NASA's Software Engineering Laboratory found that increased quality assurance was associated with decreased error rate but did not increase overall development cost. Statistical analysis conducted at IBM found that software projects with the lowest levels of defects had the shortest development schedules and the highest development productivity. They explained their findings by staying that software defect removal is actually the most expensive and time-consuming form of work for software (Jones, Capers 2000: “Software Assessments, Benchmarks, and Best Practices.” Addison-Wesley).

Because the duration at which a software defect stays in a program is directly correlated with the cost of fixing it, quality practices should focus on solutions that enable to remove and/or detect errors as soon as possible in each stage of the project lifecycle. Indeed other studies have proven that one of the most important cost factors is the time in which errors are found. The longer the defect stays in the software production chain, the more damage it causes further down the chain. Since requirements are done first, requirements defects have the potential to be in the system longer and to be more expensive. Defects inserted into the software upstream also tend to have broader effects than those inserted further downstream.


Average Cost of Fixing Defects Based on When They're Introduced and Detected

Time Detected 

Time Introduced 

Requirements 

Architecture 

Construction

System Test

Post-Release

Requirements

1

3

5-10

10

10-100

Architecture

-

1

10

15

25-100

Construction

-

-

1

10

10-25

 

Based on book “Code Complete” and studies* -> Fagan 1976; Leffingwell 1997; Willis et al. 1998; Grady 1999; Shull et al. 2002; Boehm and Turner 2004.

The bowling game Kata

Here is a kata made by uncle Bob (Robert C. Martin).  It should be resolved the TDD way (you know…by writing the test first).  The user story of the kata sounds like:
“The game consists of 10 frames as shown above. In each frame the player has two opportunities to knock down 10 pins. The score for the frame is the total number of pins knocked down, plus bonuses for strikes and spares.

A spare is when the player knocks down all 10 pins in two tries. The bonus for that frame is the number of pins knocked down by the next roll. So in frame 3 above, the score is 10 (the total number knocked down) plus a bonus of 5 (the number of pins knocked down on the next roll.)
A strike is when the player knocks down all 10 pins on his first try. The bonus for that frame is the value of the next two balls rolled. In the tenth frame a player who rolls a spare or strike is allowed to roll the extra balls to complete the frame. However no more than three balls can be rolled in tenth frame.”

You can find my C# solution here, but you should not look at it before you tried to implement it by yourself.

Have fun…

Create an offline HTML5 application for IOS with ASP.NET MVC3

 You need to build an application for the IPad or IPhone but you don’t have the time to learn objective-c and cocoa.  The good news is that you don't need to! 

This walkthrough demonstrate how to use the .Net web technology stack to create applications for the IOS platform.  Because we want to be capable of using the application also offline, the UI need to rely purely on HTML and Javascript and we can't use any server side markup generation. Nevertheless we'll need ASP.NET MVC for the creation of web services and his brand new model binding feature to deserialize the JASON message back to a server side model.

We'll first create a new Visual Studio Asp.Net MVC3 empty web application and add an “index.htm” page to the project root:

<!DOCTYPE html>
<html>
<head>
    <title>Subscription Form</title>
    <link href="Content/Site.css" rel="stylesheet" type="text/css" />
    <script src="/Scripts/jquery-1.5.1.min.js" type="text/javascript"></script>
    <script src="/Scripts/jquery.validate.min.js" type="text/javascript"></script>
</head>
<body>
    <div class="page">
        <header>
            <div id="title">
               Subscription Form
            </div>
 
        </header>
        <section id="main">
        <span id="resultmessage"></span>
        <form id="sub_form" method="" action="">
            <table>
                <tr>
                    <td>
                        Firstname:
                    </td>
                    <td>
                        <input type="text" id="firstname" name="firstname" />
                    </td>
                    <td>
                        Lastname:
                    </td>
                    <td>
                        <input type="text" id="lastname" name="lastname" />
                    </td>
                </tr>
                <tr>
                    <td>
                        E-mail:
                    </td>
                    <td>
                        <input type="text" id="email" name="email" />
                    </td>
                    <td>
                        Phone:
                    </td>
                    <td>
                        <input type="text" id="phone" name="phone" />
                    </td>
                </tr>
                <tr>
                    <td>
                        <input type="button" id="create_subscription" value="Create" >
                    </td>
                    <td>
                        <input type="button" id="send_data" value="Send" />
                    </td>
                </tr>
            </table>
        </form>
        <footer>
            <div>
                <span id="busy"><img src="/Content/wait.gif" alt="wait" /></span>
            </div>
        </footer>
        </section>
    </div>
    <script src="/Scripts/index.js" type="text/javascript"></script>
</body>
</html> 

The first line uses the Html5 doctype and the header links to jQuery. We uses two buttons, one to create the profiles in the browser local storage and another the send the profiles to the server.

To create red boxes arround our input boxes when jQuery detects some validation we need to add these few lines of css to stylesheet:

input.error { border: 2px solid red; }

Now it’s time to implement our UI logic with javascript. By creating a separate file for our javascript we keep the  html page clean.

Add a “index.js” to the script folder:

var root = '/Subscription';
var subscriptionsUploaded = 0;
 
//jQuery on document ready handler
$(function () {
    //Show the busy indicator during ajax calls
    $("#busy").hide();
    $("#busy").ajaxStart(function () { $("#busy").show(); })
    $("#busy").ajaxStop(function () { $("#busy").hide(); })
    var x = $("#createSubscription");
    //When the createSubscription button is clicked create a subscription
    $("#create_subscription").click(createSubscriptionClick);
 
    //When the sendData button is clicked retrieve the subscriptions stored in the local storage and send the data to the server
    $("#send_data").click(sendDataClick);
 
    //Define jQuery validation rules and execute the validation when the form is validated 
    $("#sub_form").validate({
        rules: {
            firstname: {
                required: true,
                minlength: 2
            },
            lastname: {
                required: true,
                minlength: 2
            },
            email: {
                required: true,
                email: true
            },
            phone: {
                required: true,
                number: true,
                minlength: 9,
                maxlength: 9
            }
        },
        messages: {
            firstname: "",
            lastname: "",
            email: "",
            phone: ""
        }
    });
});
 
function sendDataClick() {
    //Iterate over the subscriptions stored in the local storage
    for (var i = 0; i < window.localStorage.length; i++) {
        //Retrieve the serialized subscription  
        var json = window.localStorage.getItem(window.localStorage.key(i));
        try {
            //Send the subscription to the server 
            sendData(
                    json,
            //On success remove the subscription from the local storage 
                    function (data) {
                        window.localStorage.removeItem(window.localStorage.key(data.Message));
                        subscriptionsUploaded++;
                        $("#resultmessage").html(subscriptionsUploaded + " subscriptions uploaded!");
                    },
            //On error
                    function (xhr) {
                        alert(xhr.responseText);
                    });
        }
        catch (e) { alert(e); }
    };
}
 
//Stores a subscription into the local storage
function createSubscriptionClick() {
    //check the jQuery validation rules 
    if ($("#sub_form").valid()) {
        var person = getSubscription();
        //seialize the subscription 
        var jsData = JSON.stringify(person);
        //store the subscription
        window.localStorage.setItem($("#email").val(), jsData);
        //update the resultMessage
        $("#resultmessage").html($("#email").val() + " stored in local storage");
        clearAll();
    }
}
 
//Create a subscription object and bind to the input boxes values
function getSubscription() {
    var firstname = $("#firstname").val();
    var lastname = $("#lastname").val();
    var email = $("#email").val();
    var phone = $("#phone").val();
    return { Firstname: firstname, Lastname: lastname, Email: email, Phone: phone };
}
 
//Clear the input boxes values
function clearAll() {
    $("#firstname").attr("value", "");
    $("#lastname").attr("value", "");
    $("#email").attr("value", "");
    $("#phone").attr("value", "");
}
 
//Ajax: post the json serilized subscriptions 
function sendData(json, success, error) {
    $.ajax({
        url: root + '/save',
        type: 'POST',
        dataType: 'json',
        data: json,
        contentType: 'application/json; charset=utf-8',
        success: success,
        error: error
    });
}

When the user click on the create button the createSubscription function is called, this function is responsible for serializing the data into a Json object and to store it in the isolated storage.  Isolated storage is a feature defined in the Html5 spec. For each domain the browser provide access  to private key/value pair collection where data can be stored  and retrieved. The sendData button is attached with an anonymous function that iterate over the key/value pair collection that contains the JSON serialized subscriptions and post the JSON serialized subscription object.

The MVC 3 includes built-in JSON binding support that enables action methods to receive JSON-encoded data and model-bind it to action-method parameters. We will now create our Model to bind to, add a SubscriptionModel class to the models of the MVC app:

public class SubscriptionModel
{
    [Required]
    [Display(Name = "Firstname")]
    public string Firstname { get; set; }
 
    [Required]
    [Display(Name = "Lastname")]
    public string Lastname { get; set; }
 
    [Display(Name = "Phone")]
    public string Phone { get; set; }
 
    [Required]
    [DataType(DataType.EmailAddress)]
    [Display(Name = "Email address")]
    public string Email { get; set; }
 
    public void Save()
    {
        //store the object in db
        //...
    }
}

The MVC3 model binder will automatically bind all properties that matches the properties in the JSON encoded data.  Now we will define our action method to post to, add a SubscriptionController class to the controllers folder:

[HttpPost]
public ActionResult Save(SubscriptionModel inputModel)
{
    //Server side validation
    if (ModelState.IsValid)
    {
        //If the model is valid we can call the save method
        inputModel.Save();
        string message = inputModel.Email;
 
        //We return the email so that when a subscription has been successfully stored we can remove it from the localstorage.
        //--> SendData function in index.js-> window.localStorage.removeItem(window.localStorage.key(data.Message));
        return Json(new { Message = message });
    }
    else
    {
        //When server side validation fails we return in the details about the validation rules that failed.
        string errorMessage = "<div class=\"validation-summary-errors\">"
                + "The following errors occurred:<ul>";
        foreach (var key in ModelState.Keys)
        {
            var error = ModelState[key].Errors.FirstOrDefault();
            if (error != null)
            {
                errorMessage += "<li class=\"field-validation-error\">"
                    + error.ErrorMessage + "</li>";
            }
        }
        errorMessage += "</ul>";
        return Json(new { Message = errorMessage });
    }
}

Now you should be able to test the application. 

Set a breakpoint on the Save method of the SubscriptionModel. When you enter data in the form and push on create profiles are added to the local storage, when you click on send you should hit the breakpoint and all properties of the model should been set.

To be able to load the application when we are offline we need to use a new feature available in the latest browser like IE9, the Html5:”offline application”.  More info of this feature can be found here.

Add a cache.manifest file to the root of the application:

CACHE MANIFEST
/index.htm
/Content/Site.css
/Scripts/index.js
/Scripts/jquery-1.5.1.min.js
/Scripts/jquery.validate.min.js
/Content/wait.gif

Change the doctype of the index.htm file:

<html manifest="cache.manifest">

You will also need to change the http header that is returned by the IIS server when serving the manifest file.  Therefore in IIS7 you need to click on HTTP Headers, open IIS console manager, navigate to the cache.manifest file, click on http headers, add for the name set-> manifest,value->text/cache-manifest.

If you want an optimal experience when deploying your app on the IPhone or IPad you should also add the following meta tags inside the HTML header:

<meta name="viewport" content="user-scalable=no, width=device-width, initial-scale=1.0, maximum-scale=1.0" />
<meta name="apple-mobile-web-app-capable" content="yes" />
<meta name="apple-mobile-web-app-status-bar-style" content="black" />
<link rel="apple-touch-icon" href="/Content/applogo.gif" />
<link rel="apple-touch-startup-image" href="/Content/startuplogo.gif" />

You should also add your own image files (applogo & startuplogo) in the content folder so that the startup and app icon is set on the IPhone.

Now you can navigate to the page with your IOS device, click on “+” button of the safari browser, add shortcut and voilà you’ve an offline application for your IPhone/IPad made entirely in HTML5/Javascript/ASP.NET MVC3.

Have fun!

kick it on DotNetKicks.com

HTML5 Local Storage

There is a feature in HTML5 that enables you to store named key/value pairs in the local storage of your browser.  The advantage over using regular cookies is that your data is never send to the remote web server and that you can store a lot more compared to the 4KB limitation of cookie storage space.

As the local storage is a feature making part of the HTML5 specification, only modern browser supports is;
Safari 4.0, Opera 10.5,IE8, FireFox 3.5, Chrome 4.0 and IOS above version 2.0.

As for any feature of HTML5 before using it you should check if your browser supports the feature:

   1: function can_use_html5() {
   2:    try {
   3:        return 'localStorage' in window && window['localStorage'] !== null;
   4:    } 
   5:    catch (e) {
   6:        return false;
   7:    }
   8: }

To store or retrieve an item from the local storage:

   1: //Retireve an item:
   2: var item = localStorage.getItem("mykey");
   3: //Store an item:
   4: localStorage.setItem("myKey", "myValue");

To clear an item from the local storage or remove all items:

   1: //Remove an item
   2: localStorage.removeItem("myKey");
   3: //Remove all
   4: localStorage.clear();

 

With this feature we are now able to build web applications that can work offline.  Here under I build an application that stores persons profiles in the local storage.  I use the JSON.stringify method to serialize the persons before storing the JSON strings in the local storage and JQuery to retrieve the values and bind the events:

   1: <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
   2: <html xmlns="http://www.w3.org/1999/xhtml">
   3: <head>
   4:     <title>Local Storage</title>
   5:     <script src="http://ajax.microsoft.com/ajax/jQuery/jquery-1.5.1.js" type="text/javascript"></script>
   6: </head>
   7:  
   8: <body>
   9:     <div id="resultMessage">&nbsp;</div>
  10:     <table style="position: absolute; left: 10px;">  
  11:         <tr>
  12:             <td>
  13:             Firstname:
  14:             </td>
  15:             <td><input type="text" id="firstname" />
  16:             </td>
  17:         </tr>
  18:         <tr>
  19:             <td>
  20:             Lastname:
  21:             </td>
  22:             <td><input type="text" id="lastname" />
  23:             </td>
  24:         </tr>
  25:         <tr>
  26:             <td>
  27:             E-mail: 
  28:             </td>
  29:             <td><input type="text" id="email" />
  30:             </td>
  31:         </tr>
  32:          <tr>
  33:             <td>
  34:                 <input type="button" id="save"  value="Create" />
  35:             </td>
  36:             <td>
  37:                 <input type="button" id="print"  value="Print"/>
  38:             </td>
  39:         </tr>
  40:     </table>
  41:     <script type="text/javascript">
  42:         $(function () {
  43:             if (!can_use_html5()) {
  44:                 alert("This browser does not support Html5 storage, upgrade or change your browser!");
  45:             }
  46:         });
  47:  
  48:         $(function () {
  49:             $("#save").click(function () {
  50:                 
  51:                 var person = getPerson();
  52:                 
  53:                 //simple validation
  54:                 if (person == null) {
  55:                     alert("Fill in all fields please!");
  56:                     return;
  57:                 }
  58:                 
  59:                 var jsData = JSON.stringify(person);
  60:                 localStorage.setItem($("#email").val(), jsData);
  61:                 
  62:                 $("#resultMessage").html($("#email").val() + " stored in local storage");
  63:                 clearAllText();
  64:             })
  65:         });
  66:  
  67:         $(function () {
  68:             $("#print").click(function () {
  69:                 for (var i = 0; i < window.localStorage.length; i++) {
  70:                     document.write('<div id="record_' + i + '"> ' + localStorage[localStorage.key(i)] + '</div>');
  71:                 }
  72:                 localStorage.clear();
  73:             })
  74:         });
  75:  
  76:         function getPerson() {
  77:             var firstname = $("#firstname").val();
  78:             var lastname = $("#lastname").val();
  79:             var email = $("#email").val();
  80:             //simple validation
  81:             return (firstname == "" || lastname == "" || email == "") ? null : { Firstname: firstname, Lastname: lastname, Email: email };
  82:         }
  83:  
  84:         function can_use_html5() {
  85:             try {
  86:                 return 'localStorage' in window && window['localStorage'] !== null;
  87:             } 
  88:             catch (e) {
  89:                 return false;
  90:             }
  91:         }
  92:  
  93:         function clearAllText() {
  94:             
  95:             $("#firstname").attr("value", "");
  96:             $("#lastname").attr("value", "");
  97:             $("#email").attr("value", "");
  98:         }
  99:     </Script>
 100: </body>
 101: </html>

HTML5 enables new possibilities for all types of scenarios like creating IPAD/IPhone web applications without having to program in objective c and/or upload the app to the app store.  In a future post I will demonstrate how you can extend this application to create such an application.   

kick it on DotNetKicks.com

Answer for Puzzle 2

public IEnumerator GetEnumerator()

{

for (int index = 0; index < values.Length; index++)

{

yield return values[(index + startingPoint) % values.Length];

}

}

This small code block of c#2 replaces the entire Iterator class. The trick here is in the yield return statement.  When you write this statement you actually ask .Net to create a state machine for you. This statement is keeping track of what we were doing when we last returned a value.  Every time the yield return statement is hit the method returns but when the calling method ask for the next element in the IEnumerable collection you re-enter the method just after the last yield return statement as you would never have left it. All the state of the local variables inside the IEnumerator method is preserved.  What is also important to understand is that trick is not performed by the runtime but by the compiler so you don’t incurs real performance lost.

Puzzle 2: iterators using the yield statement (intermediate)

Remove the iterator class from the listing and replace the call to the iterator class, inside the iteration sample(line 29), by using the yield operator of C#2.

The output of the Problem() method should remain the same.

  1: using System;
  2: using System.Collections;
  3: 
  4: namespace Puzzles
  5: {
  6: 
  7:     public class Enumarators
  8:     {
  9:         public static void Problem()
 10:         {
 11:             var myValues = new[] { "a", "b", "c", "d" };
 12:             var col = new IterationSample(myValues);
 13:             foreach (var x in col)
 14:             {
 15:                 Console.WriteLine(x);
 16:             }
 17:         }
 18: 
 19:         public class IterationSample : IEnumerable
 20:         {
 21:             internal object[] values;
 22: 
 23:             public IterationSample(object[] values)
 24:             {
 25:                 this.values = values;
 26:             }
 27:             public IEnumerator GetEnumerator()
 28:             {
 29:                 return new Iterator(this);  //Change this line by using yield
 30:             }
 31:         }
 32: 
 33:         public class Iterator:IEnumerator
 34:         {
 35:             IterationSample parent;
 36:             int position;
 37: 
 38:             internal Iterator(IterationSample parent)
 39:             {
 40:                 this.parent = parent;
 41:                 position = -1;
 42:             }
 43:             public bool MoveNext()
 44:             {
 45:                 if (position != parent.values.Length)
 46:                 {
 47:                 position++;
 48:                 }
 49:                 return position < parent.values.Length;
 50:             }
 51:             public object Current
 52:             {
 53:                 get
 54:                 {
 55:                     if (position==-1 || position==parent.values.Length)
 56:                     {
 57:                      throw new InvalidOperationException();
 58:                     }
 59:                     return parent.values[position];
 60:                 }
 61:             }
 62:             public void Reset()
 63:             {
 64:                 position = -1;
 65:             }
 66:         }
 67:     }
 68: }
 69: 

Answer for puzzle 1

The result is: xhello The thing here is that because the stringbuilder is a reference type, when you change the content of the stringbuilder inside the method AppendHello() this change is reflected in the caller method –> Problem() but when you set the stringbuilder to null this change is not visible inside the caller method. This is because in .Net all parameters are passed by value, even reference types. For reference types it’s not the value of the object itself that is passed by value but a pointer to the underlying value (the reference). The pointer is copied on the stack and is passed by value. When you change the value inside the called method this change is reflected in the underlying value and is visible for the calling method. When you set the pointer to null inside the called method you only destroy the copied pointer not the underlying value.

Puzzle 1: Passing reference types by value (Beginner)

With this post I’ll start a series about programming puzzles and kata’s.

These small exercises will focus on the specific features of C#1,2 & 3.

Have fun…

 

What is the output of: ValueRef.Problem() ?

Can you explain why?

 

  1: public class ValueRef
  2: {
  3:         public static void Problem()
  4:         {
  5:             StringBuilder myStrBuilder = new StringBuilder("x");
  6:             AppendHello(myStrBuilder);
  7:             Console.WriteLine(myStrBuilder.ToString());
  8:             Console.ReadLine();
  9:         }
 10: 
 11:         static void AppendHello(StringBuilder builder)
 12:         {
 13:             builder.Append("hello");
 14:             builder = null;
 15:         }
 16: }
kick it on DotNetKicks.com

Sharing Common Volatile Assemblies Across Solutions and Teams

 

I've taken over the role of Architect on a large project made of several applications all sharing a lot of common assemblies.

 

The project is composed of several sub teams, every team is responsible for an application and share multiple common assemblies (sort of framework). Because the project is still in an early stage, the common assemblies incurs a lot of modifications on implementation but also on contract level. Therefor common assemblies are generated by custom CI (Continous Integration)builds that drops the assemblies on a network folder.  The developers working on the applications, references the common assemblies through the network folder so that when a new version of the assembly is build, it is automatically refreshed by Visual Studio. 

 

 

 

 

 

This chaotic architecture and wrong way of working brings a lot of trouble. First of all, the developers are not isolated from each others changes. Because the applications are directly referencing the assemblies stored on the network share, every time a CI build generate a new version of the common assemblies - these are automatically picked-up by Visual Studio. When breaking changes are introduced in the common assemblies, dependent applications suddenly breaks.
e.g. -> When the developer A check’s in a file belonging to a shared project this trigger a CI build and refreshes the shared assembly on the build output folder –  Visual Studio of developer B detect that the referenced assembly has changed and refreshes the local bin folder - potentialy breaking the build of B and this without performing a get-latest version.

The teams also experiences a lot of runtime errors caused by not matching assembly manifests. The problem is that the common assemblies are versioned and also dependent on other common assemblies.
 e.g. -> App A depend on Common1-V1 and Common2-V1. Common1-V1 depend on common3-V1 & Common2-V1 depend on Common3-V2. This leads to runtime errors because only 1 assembly version of Common3 can be in the app bin folder at the same time. When the application creates a new instance of Common3 (not the right version) the runtime detect that the assembly manifest of Common3 is different from the expected version and throws a fatal error.

Despite all the pain, the teams didn't want to perform a big architectural refactoring right now – they are behind schedule and needs to release a first version of the applications next month.  So I searched for an efficient way to bring some order in this chaos without impacting the project too much.

The first thing was to change the references in the VS soltions. I created a local “_Reference” folder on the root of every VS solution and copied all tools and shared assemblies into this folder. I deleted and re-created all references so that they now point to the local "_Reference" folder. This improve the build time and isolate the teams from each other changes - but the spaghetti of dependencies was still causing a lot of trouble. The teams still needed to constanly update their local _Reference folder with the latest version of the common assemblies  otherwise they could experience integration issues later on. But copying constantly the common assemblies is error prone and tedious.

For this project automating the syncing of the local references was a must, therfore I made a small tool: JoPack. I grouped the shared assemblies in packages and created a central xml file representing our project catalogue.
The catalogue is basically an xml file defining the packages and the files included in those packages and the path (source) where they can be found. It also describes the dependencies between the packages. I added a local dependency.xml file to each solution root path. This file lists the packages used by a particular solution. The catalogue is versioned on our source controller and available through a network share.

 

Based on the dependency.xml, JoPack synchronizes the local reference folder with the shared files.

 

JoPack download all the packages present in the dependency.xml file but also other packages the root packages are dependent on.
Our shared assemblies listed in the catalog.xml file are still created by CI builds and put on the network shares but now each time a new version of an assembly is created a developer can use JoPack to synchronize his local reference folder. Once the solution is build and all tests are passing the local reference folder can be checked-in making it available to all other developers working on the same application.
We also automated this process by running JoPack as part of our CI build:
We setup a pre-build target that launches JoPack to sync the solution local reference folder on the CI server. When the build succeed the CI build check the _Reference folder back in. Now the developers don’t need to run JoPack on their local machine anymore.

 This strategy decreased the complexity due to the number of dependenices because the number of shared parts went from 100 assemblies to 15 packages. The dependencies are also more obvious because we now dispose of a catalogue that provides an overview of all the dependencies between packages. As only 1 version of a particular assembly can be listed in the catalogue the problem of non matching assembly manifest was also solved.  What was very apreciated by the developers is that the build time decreased drastically and that they didn't experienced suddenly breaking builds anymore.

 

 

Sources, binaries and docs for JoPack can be downloaded here – contribution can be made through the jopack google code repository.

kick it on DotNetKicks.com