Over a million developers have joined DZone.

A tale of re-usablity - refactoring MangoTree parts

· Mobile Zone

Learn how to Deliver Better Mobile Apps Faster with Continuous Quality by managing the complexities of testing multiple devices and scenarios with this whitepaper from Perfecto Mobile.

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.

Do you know Why Apps Succeed? Perfecto Mobile analyzed over 1,000 responses to their Digial Quality Strategies survey and aim to answer the question, "Why do apps succeed?" in this exclusive report.

Topics:

The best of DZone straight to your inbox.

SEE AN EXAMPLE
Please provide a valid email address.

Thanks for subscribing!

Awesome! Check your inbox to verify your email so you can start receiving the latest in tech news and resources.
Subscribe

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

{{ parent.tldr }}

{{ parent.urlSource.name }}