Improve Your Methods Definition (Part 3)

Improving methods definition would dramatically enhance your code readability and maintainability. The following are major practices that need to be considered while defining your methods.


Parameters & Overload Methods

Before digging deep, we need to define in brief what an overload method is. Basically, an overload method is having two or more methods within the same class, whereas these methods share the same exact name but differs from each other in one or more of the following:
  • Parameters data types.
  • Number of parameters.
  • Order of parameters.
So initially, and based on the previous definition, overload methods are all about changing the signature of a method based on the manipulation in its passed parameters, which would lead – if not used properly and wisely – of having multiple versions of the method not knowing which version is being used, and what is the value behind having all these methods definitions, thus:

  • Make sure to use overload methods only if you are in need indeed, this would avoid defining misleading and unnecessary methods definitions. Instead try to give a meaningful naming for your methods to avoid using overload in such specific cases (which we’ll cover later in this article as part of the naming convention practice). Making a method more flexible for different parameters data types is one of the good and common practices for overload; also the same practice is used for overloaded constructors.
// Bad Practice
public class Employee
    {
        // For Permanent Employees
        public void CreateContract(int id)
        {
            // Do some logic here
        }

        // For Contract Employees
        public void CreateContract(int id, 
                                   DateTime fromDate, 
                                   DateTime toDate)
        {
           // Do some logic here
        }
    }

// Good Practice
    public class Employee
    {
        // For Permanent Employees
        public void CreatePermanentContract(int id)
        {
            // Do some logic here
        }

        // For Contract Employees
        public void CreateFixedContract(int id, 
                                        DateTime fromDate, 
                                        DateTime toDate)
        {
           // Do some logic here
        }
    }
  • When you use overload methods, use them wisely, properly and make sure that you have added enough XML notation to your overloaded methods (which we’ll cover later in this article). One of the most common bad practices is overloading the method because we didn't understand or probably we don’t want to know how the old method works because of its complexity, thus it’s easier to overload the method and rewrite it based on our current understanding. As the long project cycle passes, while the same method kept changing and maintained, we would end up of having multiple signatures for the same method, but with only one version used!!!
// Good Practice
    public class Employee
    {
        // Get Employee Information by ID
        public void GetEmployeeInformation(int id)
        {
            // Do some logic here
        }

        // Get Employee Information by Social Security
        public void GetEmployeeInformation(string socialSecurity)
        {
           // Do some logic here
        }
    }

Another good practice, and based on the previous point:

// Good Practice
    public class Employee
    {
        // Get Employee Information by ID
        public void GetEmployeeInformationById(int id)
        {
            // Do some logic here
        }

        // Get Employee Information by Social Security
        public void GetEmployeeInformationBySocialSecurity(string socialSecurity)
        {
           // Do some logic here
        }
    }

We can’t ignore the impact of methods parameters on your code shape. Do your best to define your methods smartly in terms of its parameters, try to make your methods flexible and usable as much as possible, by trying to foresee expected future requirements and modifications. Take the following example:

// Bad Practice
    public class Math
    {
        // Add Two Integers
        public int Add(int a, int b)
        {
            return a + b;
        }

        // Add Three Integers
        public int Add(int a, int b, int c)
        {
            return a + b + c;
        }
    }

As the parameters grow, you will overload your method or may be modify it to fit the new requirements, while you can do the following to serve all possible scenarios:

// Good Practice
    public class Math
    {
        // Addition
        public int Add(int[] integersToAdd)
        {
            int result = 0;
            foreach (int entry in integersToAdd)
            {
                result += entry;
            }
            return result;
        }
    }

In addition to what have been mentioned before, and in case your method parameters kept growing exponentially, try to encapsulate your method parameter in one object, which includes all your parameters as properties.

Finally, remember that the latter notes are more related to overloaded methods, than to overloaded constructors, thus the mentioned practices don’t apply for them.

Reusable Global Methods

In any program there are methods that are used frequently and widely in your project, so instead of repeating the definition of these methods in each place there’re required, it’s easier to define a public class(es) that contains your reusable methods, in order to make them reusable and accessible all over your project code; however in some cases you might use public static methods (that can be called directly), whereas this applies only for native utility methods which don’t pose risks to expand – GetCurrentLangauge or SendNotifications for an example, which usually never tend to change at all – or to methods with no requirements for future change and maintenance, nevertheless bear in mind that using the latter approach as a general main standard for reusable methods is not recommended due to the following reasons*:
  • Polymorphism: Say we have the method Helper.SomeMethod that happily buzzes along. Suddenly, we need to change the functionality slightly. Most of the functionality is the same, but we have to change a couple of parts nonetheless. Had it not been a static method, we could make a derivate class and change the method contents as needed. As it's a static method, we can't. Sure, if we just need to add functionality either before or after the old method, we can create a new class and call the old one inside of it - but that's just gross.
  • Interface Woes: Static methods cannot be defined through interfaces for logic reasons. And since we can't override static methods, static classes are useless when we need to pass them around by their interface. This renders us unable to use static classes as part of a strategy pattern. We might patch some issues up by passing delegates instead of interfaces.
  • Testing: This basically goes hand in hand with the interface woes mentioned above. As our ability of interchanging implementations is very limited, we'll also have trouble replacing production code with test code. Again, we can wrap them up but it'll require us to change large parts of our code just to be able to accept wrappers instead of the actual objects.
  • Fosters Blobs: As static methods are usually used as utility methods and utility methods usually will have different purposes, we'll quickly end up with a large class filled up with non-coherent functionality - ideally, each class should have a single purpose within the system. I'd much rather have a five times the classes as long as their purposes are well defined.
  • Parameter Creep: To begin with, that little cute and innocent static method might take a single parameter. As functionality grows, a couple of new parameters are added. Soon further parameters are added that are optional, so we create overloads of the method (or just add default values, in languages that support them). Before long, we have a method that takes 10 parameters. Only the first three are really required, parameters 4-7 are optional. But if parameter 6 is specified, 7-9 are required to be filled in as well... Had we created a class with the single purpose of doing what this static method did, we could solve this by taking in the required parameters in the constructor, and allowing the user to set optional values through properties, or methods to set multiple interdependent values at the same time. Also, if a method has grown to this amount of complexity, it most likely needs to be in its own class anyways.
   // Bad Practice (E-mail notification can be used frequently and                                 widely)
    public class Employee
    {
        public void SalaryRelease()
        {
            // Do some logic

            // Send notification
            SendNotificationEmail("employee@Company.com"
                                  "finance@Company.com"
                                  "Salary Release"
                                  "You Salary Have Been Released");
        }

        // Send Notification E-mail
        private void SendNotificationEmail(string toEmail, 
                                           string fromEmail, 
                                           string subject,                                                                              string body)
        {
            MailMessage message = new MailMessage();
            message.To.Add(toEmail);
            message.Subject = subject;
            message.From = new MailAddress(fromEmail);
            message.Body = body;
            SmtpClient smtp = new SmtpClient("YourSMTP");
            smtp.Send(message);
        }
    }

   // Good Practice
    public class Employee
    {
        public void SalaryRelease()
        {
            // Do some logic

            // Send notification
            Helper.SendNotificationEmail("employee@Company.com"
                                         "finance@Company.com"
                                         "Salary Release"
                                         "You Salary Have Been Released");
        }
    }


    public class Helper
    {
        // Send Notification E-mail
        public static void SendNotificationEmail(string toEmail, 
                                                 string fromEmail, 
                                                 string subject, 
                                                 string body)
        {
            MailMessage message = new MailMessage();
            message.To.Add(toEmail);
            message.Subject = subject;
            message.From = new MailAddress(fromEmail);
            message.Body = body;
            SmtpClient smtp = new SmtpClient("YourSMTP");
            smtp.Send(message);
        }
    }

   // Bad Practice
    public class Employee
    {
        public void SalaryRelease()
        {
            try
            {
                // Do some logic
            }
            catch (Exception ex)
            {
                string eventLogSource = "HR-System";
                EventLog log = new EventLog();
                if (!EventLog.SourceExists(eventLogSource))
                {
                    EventLog.CreateEventSource(eventLogSource, "Application");
                }
                log.Source = eventLogSource;
                log.WriteEntry(ex.Message, EventLogEntryType.Error, 911);
            }
        }

        public void SubmitLeave(int id, 
                                DateTime from, 
                                DateTime to, 
                                string contactInformation, 
                                string description)
        {
            try
            {
                // Do some logic
            }
            catch (Exception ex)
            {
                string eventLogSource = "HR-System";
                EventLog log = new EventLog();
                if (!EventLog.SourceExists(eventLogSource))
                {
                    EventLog.CreateEventSource(eventLogSource, "Application");
                }
                log.Source = eventLogSource;
                log.WriteEntry(ex.Message, EventLogEntryType.Error, 911);
            }
        }
    }

   // Good Practice
    public class Employee
    {
        public void SalaryRelease()
        {
            try
            {
                // Do some logic
            }
            catch (Exception ex)
            {
                HRExceptions error = new HRExceptions();
                error.LogException(ex.Message);
            }
        }

        public void SubmitLeave(int id, 
                                DateTime from, 
                                DateTime to, 
                                string contactInformation, 
                                string description)
        {
            try
            {
                // Do some logic
            }
            catch (Exception ex)
            {
                HRExceptions error = new HRExceptions();
                error.LogException(ex.Message);

            }
        }
    }

    public class HRExceptions
    {
        // Log HR System Errors
        public void LogError(string error)
        {
            string eventLogSource = "HR-System";
            EventLog log = new EventLog();
            if (!EventLog.SourceExists(eventLogSource))
            {
                EventLog.CreateEventSource(eventLogSource, "Application");
            }
            log.Source = eventLogSource;
            log.WriteEntry(error, EventLogEntryType.Error, 911);
        }
    }

Branching

A branch is basically a piece of code sequence in your method which is conditionally executed depending on the flow of your code. However, having too many branches or using unstructured (unconditional) branching would make your code implement one of the main characteristics of a spaghetti code, whereas the major two reasons for a bad branching practice are inexperienced programmers and a complex code which have been modified over the long project life cycle.


Why to do it like this???
ç
While it can look like that!!!
è

Here are good tips to follow when branching is used in your code flow:
  1. Avoid using “goto” at all times, whereas using “goto” is one of the main characteristics of having an unstructured code flow.
  2. Avoid using more than 2 - 3 levels of methods branching, in other words avoid multiple nested branching statements.
  3. Avoid using too many exception handlers.
  4. Nevertheless, avoid using big functions, if your function is too long then refactor it, however take all of the above points into consideration.


Comments

Popular posts from this blog

Counting Down to ESPC21 Online

Machine Learning Explained!

Building JSON Objects in Microsoft Flow