Over a million developers have joined DZone.
{{announcement.body}}
{{announcement.title}}

A tale of re-usablity - refactoring MangoTree parts

DZone's Guide to

A tale of re-usablity - refactoring MangoTree parts

· Mobile Zone
Free Resource

Download this comprehensive Mobile Testing Reference Guide to help prioritize which mobile devices and OSs to test against, brought to you in partnership with Sauce Labs.

As I am working on MangoTree - a Twitter client for Windows Phone, I find myself modifying tons of code that I wrote at the very beginning, adapting it to new test cases and scenarios. One of the main elements in my application is the OAuthClient class - its purpose is organize the OAuth autentication flow with all the necessary methods. However, the problem was that I accidentally started using it as my Twitter connection layer. 

One of the rules that I broke is the fact that the code will not be reusable. OAuth is not Twitter-specific, so why exactly am I trying to process data retrieved through the Twitter API in this class? I had a switch statement inside the GetResponse callback method, as well as a specific callback for status updates. This pretty much reduced the portability of my code to zero. I worked on adjusting some of the OAuthClient elements and ended up with something like this:

using System;
using System.Net;
using System.Collections.Generic;
using System.Linq;
using MangoTree.Utility;
using System.Security.Cryptography;
using System.Text;
using System.IO;

namespace MangoTree.Twitter
{
    public class OAuthClient
    {
        private Action<object[]> CompletionAction { get; set; }
        private RequestType TypeOfRequest { get; set; }

        public void PerformRequest(Dictionary<string, string> parameters, string url, string requestMethod, string consumerSecret, string token, string contentType, Action<object[]> completionAction, RequestType requestType)
        {
            CompletionAction = completionAction;
            TypeOfRequest = requestType;

            HttpWebRequest request = (HttpWebRequest)WebRequest.Create(url);
            request.Method = requestMethod;
            request.ContentType = contentType;

            string OAuthHeader = GetOAuthHeader(parameters, requestMethod, url, consumerSecret, token);
            request.Headers["Authorization"] = OAuthHeader;

            request.BeginGetResponse(new AsyncCallback(GetResponse), request);
        }

        private void GetResponse(IAsyncResult result)
        {
            HttpWebRequest request = (HttpWebRequest)result.AsyncState;

            if (TypeOfRequest == RequestType.Read)
            {
                HttpWebResponse response = (HttpWebResponse)request.EndGetResponse(result);

                using (StreamReader reader = new StreamReader(response.GetResponseStream()))
                {
                    string completeString = reader.ReadToEnd();

                    CompletionAction(new object[] { completeString });
                }
            }
            else
            {
                CompletionAction(new object[] { request, result });
            }
        }

        private string GetOAuthHeader(Dictionary<string, string> parameters, string httpMethod, string url, string consumerSecret, string tokenSecret)
        {
            parameters = parameters.OrderBy(x => x.Key).ToDictionary(v => v.Key, v => v.Value);

            string concat = string.Empty;

            string OAuthHeader = "OAuth ";

            foreach (string k in parameters.Keys)
            {
                if (Parameters.Sanitized.Contains(k))
                    concat += k + "=" + StringHelper.SanitizeStatus(StringHelper.EncodeToUpper(parameters[k])) + "&";
                else
                    concat += k + "=" + parameters[k] + "&";

                if (!Parameters.Banned.Contains(k))
                    OAuthHeader += k + "=" + "\"" + parameters[k] + "\", ";
            }

            concat = concat.Remove(concat.Length - 1, 1);
            concat = StringHelper.EncodeToUpper(concat);

            concat = httpMethod + "&" + StringHelper.EncodeToUpper(url) + "&" + concat;

            byte[] content = Encoding.UTF8.GetBytes(concat);

            HMACSHA1 hmac = new HMACSHA1(Encoding.UTF8.GetBytes(consumerSecret + "&" + tokenSecret));
            hmac.ComputeHash(content);

            string hash = Convert.ToBase64String(hmac.Hash);
            hash = hash.Replace("-", "");

            OAuthHeader += "oauth_signature=\"" + Uri.EscapeDataString(hash) + "\"";

            return OAuthHeader;
        }
    }
}

First of all, there is now an Action<object[]> parameter - I have no idea what I am going to do with the returned data. Today, it can be a JSON string, tomorrow I might decide to switch to XML. If the processing layer is integrated in GetResponse, I get no flexibility whatsoever. With a referenced delegate, the action taken can be anything - all I need are the passed secondary objects.

Notice that there is also a RequestType, that shows whether the request reads or writes data. This ultimately affects how the data is processed in the Action and what data is passed further to the application.

The HTTP methods and content types are also passed independently - even though there is a limited number of those actually used, changes might occur on the platform side and it's better to be prepared.

GetResponse now has zero decision-making responsibilities other than taking the appropriate action depending on whether the data is read or written to the response stream. All this brings me to the following conclusions:

1. There is nothing wrong with writing code "that just works" at the beginning - after all, I needed to make sure that the application is doing what it is supposed to do and that every part of it returns correct results.

2. The code that "just works" becomes harder to maintain as the application grows - with small projects, I probably would've left the code I mentioned above untouched, especially if the purpose of my application was extremely basic (e.g. just update a status). However, as the application becomes more complex, the same code might cause a headache when it comes to re-organizing the app flow.

3. As the application grows, make sure that classes abide their purpose rules - if it is an OAuth class, it should not parse user data and return models. No, and again - no. That class can return raw data and other processing elements should decide what to do with it.

4. No code stays the same for long - even the most well-planned projects will need modifications. Write code in such a way that you can modify it later.

5. Think about other situations where your code can be re-used - can I use my OAuth layer for Facebook? If the answer is yes, then I wrote it well. If the answer is no, I need to re-write that layer. This will benefit everybody in the long run.

Analysts agree that a mix of emulators/simulators and real devices are necessary to optimize your mobile app testing - learn more in this white paper, brought to you in partnership with Sauce Labs.

Topics:

Opinions expressed by DZone contributors are their own.

{{ parent.title || parent.header.title}}

{{ parent.tldr }}

{{ parent.urlSource.name }}