3
\$\begingroup\$

When trying to follow single responsibility principle, I often find myself in a position where there is so much to do that I do not know where to start. Maybe there is there a rule of thumb that I could use?

A concrete example originating from my test project.

    // doing way too many things
    public InvoiceDto GetInvoice(CartDto cart)
    {
        if (cart == null)
            throw new ArgumentNullException(nameof(cart));                                      // validation

        var cartequipments = _carts.GetById(cart.CartId).CartEquipments.ToList();               // quering
        var rentals = cartequipments.Select(o =>                                                // mapping
            new RentalDto                                                                       // object creation
            {
                Name = o.Equipment.EquipmentName,
                Price = _mapPriceCalculatorLogic                                                // buisiness logic
                    .Create(o.Equipment.EquipmentType)
                    .Calculate(o.RentDurationDays)
            }).ToList();
        var loyaltyPoints = _loyaltyPointsService                                               // buisiness logic
            .GetLoyaltyPoints(cartequipments.Select(o => o.Equipment.EquipmentType)); 
        var total = rentals.Sum(o => o.Price);                                                  // aggregation

        var invoiceDto = new InvoiceDto                                                         // object creation 
        {
            Title = $"Invoice id : {cart.CartId}",
            Rentals = rentals,
            LoyaltyPoints = loyaltyPoints,
            TotalPrice = total
        };

        return invoiceDto;
    }

My question is how would you clean this up and if possible what would the result look like (helper methods sources not needed).

\$\endgroup\$
4
  • \$\begingroup\$ Please provide a title which indicates what your code does. Your current title is very generic. \$\endgroup\$
    – Mast
    Commented May 19, 2016 at 9:39
  • \$\begingroup\$ No; read what @Mast said again - your title should describe what your code does, not the outcome of the review that you are hoping for :) \$\endgroup\$
    – Dan
    Commented May 19, 2016 at 9:49
  • \$\begingroup\$ No. Please take a look at How to write good titles. \$\endgroup\$
    – Mast
    Commented May 19, 2016 at 9:51
  • \$\begingroup\$ @DanPantry Is it better? \$\endgroup\$
    – Margus
    Commented May 19, 2016 at 9:55

2 Answers 2

2
\$\begingroup\$

Make separate methods for getting equipment from the cart, creating rentals from the equipment, getting loyalty points from the equipment, and creating the invoice from the rentals and loyalty points.

\$\endgroup\$
2
  • \$\begingroup\$ You get my "up vote", because it is correct and it should be the next thing I do. However I was thinking several steps forward - where should those methods belong. Is it ok for validation to be in method if it is just on null check - what would change if I need to add more? \$\endgroup\$
    – Margus
    Commented May 19, 2016 at 13:56
  • \$\begingroup\$ This requires too much elaboration in a single response, so for now, I will recommend having different assemblies for Shopping, Rentals (because you need to manage rental), Loyalty Programme, and Billing. Then, the equivalent of your original method above will sit in a "bridge" assembly (look up Bridge Pattern) that coordinates the operations of reading the cart, setting up the rentals, calculating the loyalty points and generating the invoice. It helps if you can express the responsibility of each assembly clearly (e.g. The Rentals assembly is responsible for...) without the "and" word. \$\endgroup\$
    – RWRkeSBZ
    Commented May 19, 2016 at 15:14
0
\$\begingroup\$

Your code is well formatted and easy to read. Congratulations.

With regards to SRP, I believe your code complies just fine. Each dependency: the cart, rentals, and loyalty points is retrieved with a single call. The mapping for those DTO are isolated in a small single purpose anonymous method. The single responsibility for the method matches its name GetInvoice.

If your uncomfortable with the idea of anonymous methods being separate from the method where they are defined, consider creating private static named methods in this method's class and referencing them.

The only SRP violation I'm concerned with is the invoice DTO. I would recommend using the raw cart id and letting your presentation code format the title.

Here's an example of how I may clean it up:

public InvoiceDto GetInvoice(CartDto cart)
{
    if (cart == null)
        throw new ArgumentNullException(nameof(cart));

    var cartequipments = _carts.GetById(cart.CartId).CartEquipments.ToList();
    var rentals = cartequipments.Select(o => MapEquipmentToRentalDto).ToList();
    var loyaltyPoints = _loyaltyPointsService
        .GetLoyaltyPoints(cartequipments.Select(o => o.Equipment.EquipmentType)); 
    var total = rentals.Sum(o => o.Price);

    var invoiceDto = new InvoiceDto 
    {
        InvoiceId = cart.CartId,
        Rentals = rentals,
        LoyaltyPoints = loyaltyPoints,
        TotalPrice = total
    };

    return invoiceDto;
}

private static RentalDto MapEquipmentToRentalDto(CartEquipment o)
{
    return new RentalDto 
    {
        Name = o.Equipment.EquipmentName,
        Price = _mapPriceCalculatorLogic
                .Create(o.Equipment.EquipmentType)
                .Calculate(o.RentDurationDays)
    };
}
\$\endgroup\$
4
  • \$\begingroup\$ CartDto contains only 1 id and this is because currently I do not need anything more (it is specifically a dto and not a type). I think it was called "Law of Demeter" what stated I should not pass in more than I actually need. If you scroll right you can see comments about what responsibility is currently violated. \$\endgroup\$
    – Margus
    Commented May 19, 2016 at 13:51
  • \$\begingroup\$ YAGNI is to not do more than you actually need. LoD is to not reach through dependencies -- think no more than one dot operator in a statement. LoD is most definitely not happy with this code block. \$\endgroup\$
    – psaxton
    Commented May 19, 2016 at 14:11
  • \$\begingroup\$ @psaxton: "LoD" means "Law of Demeter" right? (i.e. Principal of Least Knowledge) \$\endgroup\$ Commented May 19, 2016 at 16:22
  • \$\begingroup\$ @GregBurghardt yes \$\endgroup\$
    – psaxton
    Commented May 19, 2016 at 19:23

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.