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:
- Avoid using “goto” at all times, whereas using “goto” is one of the main characteristics of having an unstructured code flow.
- Avoid using more than 2 - 3 levels of methods branching, in other words avoid multiple nested branching statements.
- Avoid using too many exception handlers.
- Nevertheless, avoid using big functions, if your function is too long then refactor it, however take all of the above points into consideration.
Comments
Post a Comment