Think with Enlab

Diving deep into the ocean of technology

Stay Connected. No spam!

How to Apply Top-Down Approach in Programming

Overview

Writing code becomes much easier when requirements are broken down into actionable items with given system architecture, database design, and UI sketch. In this article, I’m going to share with you how to apply the top-down approach in your programming work and some useful tips to improve your efficiency at work as well as the quality of codes.

Top-Down Approach in Programming

What is a Top-down Approach?

A top-down approach is about breaking down a system into the subsystems that make it up. The process can be repeated to break down subsystems into low-level elements like classes and methods. This approach can be applied to all levels from high-level system architecture to low-level functionality implementation, just to remember to start from the top. It doesn’t necessarily always be the absolute top.

How to apply the Top-down Approach in Programming?

Defining the necessary steps before implementing a method will give you a clear insight into the method and help you structure your codes well. Here are what you should do to achieve this:

  • Stage 1: Break down the method's logic into steps using comments, as shown in the example below.
  • Stage 2: Generate dependent methods, classes, enums, etc. used in stage 1. For now, just generate empty dependent methods or classes and don’t bother implementing them during this stage.
  • Stage 3: Once you have the code skeleton, implement dependent methods one by one and run the unit test.

An Example of Top-down Approach in Programming

Below is an example of how to implement a method to perform a payment schedule. 

Firstly, write a kind of Pseudocode (C# code actually but it doesn’t compile since it lacks undefined submethods) that represents the business logic of the payment schedule.

 

private ProccesingResult<IList<PaymentScheduleDetail>> SchedulePayments(PaymentSchedule schedule)

{

       // 1. Validate schedule input

       //  (In case method IsScheduleValid doesn't have more than 5 lines, write it inline)

 

       ProccesingResult<IList<PaymentScheduleDetail>> result = IsScheduleValid(schedule);

       if (result.ErrorCode != ErrorCodes.None)

                       return result;

       // 2. Call EPIC API to schedule the payments

       //  (If you need to have more than 5 lines, define a method SchedulePaymentViaEPIC(), 

       // else write it  inline)

       if (SchedulePaymentViaEPIC(schedule))

       {

                     // 3. Upon success, call EPIC API to get payments just scheduled to return to client

                     // (If you need to have more than 5 lines,  define a method

         // SchedulePaymentViaEPIC(),  else write it inline)

                     result.Data = GetPaymentsViaEPIC(schedule);

       }

       //

       // The 3 steps (1., 2., 3.) defined above are steps of the method’s workflow. 

      // Do not write any line of code unless you have defined this workflow.

       //

       return result;

}

 

 

Secondly, as the method skeleton is written, go on to generate required sub-methods and classes.

 

private IList<PaymentScheduleDetail> GetPaymentsViaEPIC(PaymentSchedule schedule)

{

       throw new NotImplementedException();

}

private bool SchedulePaymentViaEPIC(PaymentSchedule schedule)

{

       throw new NotImplementedException();

}

private ProccesingResult<IList<PaymentScheduleDetail>> IsScheduleValid(PaymentSchedule schedule)

{

       throw new NotImplementedException();

}

internal enum ErrorCodes

{

       None = 0

}

internal class ProccesingResult<T>

{


}

internal class PaymentScheduleDetail

{


}

 

 

Finally, once all sub-methods and classes are generated (not implemented yet), continue to implement one by one and conduct unit testing to ensure it works as expected.

 

private ProccesingResult<IList<PaymentScheduleDetail>> IsScheduleValid(PaymentSchedule schedule)

{

       var result = new ProccesingResult<IList<PaymentScheduleDetail>>();

       // TODO: Validation logic implementation

       // Below is sample code to demonstrate the usage of ProccessingResult class

       result.ErrorCode = ErrorCodes.InvalidInput;

       result.ErrorMessage = "Fields are missing input or in invalid format";

       result.ErrorData = "RequiredFields:Field1,Field2;InvalidFormatFields:Field3,Field4";


       return result;

}

internal enum ErrorCodes

{

       None = 0,

       InvalidInput = 1

}

internal class ProccesingResult<T>

{

       public T Data { get; set; }

       public ErrorCodes ErrorCode { get; set; }

       public string ErrorMessage { get; set; }

       public string ErrorData { get; set; }


       public ProccesingResult()

       {

                       ErrorCode = ErrorCodes.None;

       }

}

More Useful Tips

Creating a class

You need to know clearly what the responsibility of a class is, and which layer/project to place that class. Don’t forget to stick with the Single Responsibility Principle.

Implementing a method

Defining only one single responsibility for each method and make it clear by using a method signature. Besides, it’s important to know exactly the necessary steps (workflow) of that method before writing it.

Refactoring codes

To deliver quality code (well structured, good readability, and maintainability), refactoring must be done frequently. Code refactoring is the process of reviewing codes and finding rooms for improvement. To refactor code effectively, you should find answers to these questions:

  • When: Only refactor when you have finished implementing the feature/functionality and it works as expected.
  • How often: Anytime when you finish implementing a functionality (a requirement item).
  • What to improve: Look at these aspects:
    • Are classes in the right places? Validate if they are placed in the correct layer of the system architecture.
    • Do methods belong to the right classes? A class normally takes a high-level responsibility of a given duty and its methods take low-level responsibilities of such duty. So the responsibilities of a class and its methods should be highly correlated. For instance, the class PurchaseOrderService should contain only methods that take responsibility for processing purchase orders like submitting an order or approving an order.
    • Do methods follow the Single Responsibility Principle?
    • Can classes be abstracted and methods can be inherited? Look for classes and methods that take similar responsibilities and see if base classes or helper classes can be created to provide inheritance and reuse.
    • Are there any duplicated code? Look for duplicated coding lines and see if methods can be extracted from these same lines and reused in all places.
  • How to refactor: Refactor small items one by one to ensure no big bang happens. Once an item is refactored, run automated test suites if you have, otherwise manually test it to ensure that your new code functions as what old code does.

Resolving issues

Issues often steal a lot of your time. How fast you can resolve an issue depending on your technical knowledge and your past experiences resolving a similar problem. I don't see any other better way than enhancing your technical knowledge and codifying your past experiences.  

Want to know what you should do? Check these out:

  • Keep learning fundamental knowledge of the technologies you're working on. For instance, if you're working with ASP.NET WebAPI, this document is a good reference for you. Once you understand the fundamentals of the technologies, you will be quickly understanding the issues and be able to point out the root causes. If you know the root causes, you're very close to resolving them.
  • Share what you have learned about the fundamentals with other colleagues to help you consolidate the knowledge.
  • Maintain your experiences: Our experiences can’t be transferred to others but our knowledge can be. It’s always good to have a handbook recording all issues you have ever faced and resolved to reference any time again. Make sure that these issues are organized in pairs of issue-solution to ease your search for known issues later on.

Final Thoughts

The top-down approach is to go from the general to the specific. By doing this, you can solve any complex problems, and easily understand what your code units do without looking into the very details.

Hopefully, the knowledge and the essential tips I shared with you in this article are useful in improving your coding efficiency. 

Thank you for reading! 

 

Contact us!

 


Reference

Problem Solving: Top-down design and Step-wise refinement, en.wikibooks.org, 2020

 


About the author

CTO CEO Enlab Software Vinh Tran Hi, my name's Vinh and I'm the CEO of Enlab Software. I'm passionate about building world-class digital products, helping my team become mature in their careers, and creating an environment where people find purpose and meaning in life.

 

Up Next

How to Secure Sensitive Data in The Configuration
October 13,2020 by Tan Nguyen
Introduction On the blog post “How to Configure .Net Core Environments With Practical Examples”, we shared...
How IIS Processes ASP.NET Core HTTP Request
September 30,2020 by Vinh Tran
Have you ever wondered what happens under the hood when you make an API call to...
Effective techniques for software engineer
September 25,2020 by Vinh Tran
Introduction Software development methodologies were introduced and practiced in every project using Waterfall which is the...
How to Inspect and Improve Memory Usage With dotMemory featured image
September 04,2020 by Vu Dao
In .NET programming, coding with care when using static variables, disposable objects, events, and threads...