| Sean Feldman 的个人资料ברוכים הבאים照片日志列表 | 帮助 |
Break It Down Into BitsI had to refactor a portion of code and decided to go with strategy pattern. Interesting thing is that the final result might look more complex, but when discussed with a fellow developer, got green light in terms of "more maintainable" and "self documenting" code result. So here I am sharing it with others for review and opinions. The problem A person is associated with a plan it is member of. Association is expressed in a plan code assigned to the person. Based on the code a person is assigned, different fields from his plan details record are pulled into calculation. Possible plan codes are PlanA, PlanB, PlanZ. Each plan is driving out the price for member, spouse, and total costs. Fields involved in calculations are:
The original code was using reflection and retrieving values based of reflective code that extracted property values based on the fact that attributes (properties) of an object would have the names that are consistent and are prefixed with the plan code. The code then would do everything-in-one-shot. 1: var planDetails = PlanDetails.Load();2: Type type = typeof(PlanDetails); 3: double memberCost 4: = type.GetProperty(planCode + "Member1").GetValue(planDetails, null) 5: + type.GetProperty(planCode + "Member2").GetValue(planDetails, null); 6: 7: double spouseCost = 8: type.GetProperty(planCode + "Spouse1").GetValue(planDetails, null) 9: + type.GetProperty(planCode + "Spouse2").GetValue(planDetails, null); 10: 11: double totalCost = memberCost + spouseCost; What are the down sides of this code - fragility.
Strategy sounded like a right ways to go. Divide and rule - split each plan calculations into its own class - PlanA, PlanB, and PlanZ. Also adding a Default plan to have a fallback mechanism. To make it all sing, a factory would create one of the plans based on the required argument - plan code. To glue it all together an abstraction for all plans is required. I considered an interface first, but since the plans share the calculations at this point, decided to do the simple thing - abstract base class Plan that would capture all the similarities and leave the descendents to fill the rest. This is an oversimplified result code (removed extra details and client-associated stuff). Factory 1: public class PlanFactory 2: {3: public static GetPlanFor(string planCode, IPlanDetails planDetails) 4: {5: switch (planCode) 6: {7: case "PlanA": 8: return new PlanA(planDetails); 9: case "PlanB": 10: return new PlanB(planDetails); 11: case "PlanZ": 12: return new PlanZ(planDetails); 13: default: 14: return new Default(); 15: } 16: } 17: }Plan 1: public abstract class Plan 2: { 3: public readonly IPlanDetails planDetails; 4: 5: protected Plan(IPlanDetails pd) 6: { 7: planDetails = pd; 8: } 9: 10: public abstract double MemberCost { get; } 11: public abstract double SpouseCost { get; } 12: public double TotalSpouseCore 13: { 14: get { return MemberCost + SpouseCost; } 15: } 16: }PlanA (similar are PlanB and PlanZ): 1: public class PlanA : Plan 2: {3: public PlanA(IPlanDetails pd) : base(pd) {} 4: 5: public override double MemberCost 6: {7: get { return planData.PlanAMember1 + planData.PlanAMember2; } 8: } 9: }Usage: 1: var plan = PlanFactory.GetPlanFor(user.GetPlanCode(), PlanDetails.Load()); 2: double memberCost = plan.MemberCost; 3: double spouseCost = plan.SpouseCost 4: double totalCost = plan.TotalCost; Now comparing the last chunk of code to the original code - some difference. Conclusions With simplified for maintainability and readability code, the complexity went up significantly. With power comes responsibility - if you want code that is easy to maintain and change, test and trace, you have to lift your skills and play by the rules. And the rules are simple
PS: to spice it up, the real code did actually have a more sophisticated behaviour down the road, which was taken into the Plan code (in case it is shared by all plans) or into individual plans when it's unique to that particular plan. The bottom line is the the 'user' code, the usage of the factory, has not changed at all, and the details of each plan where left to the plans themselves, a place where they naturally belong. PSS: there's something bugging me down - the switch statement in the factory. I would rather have something that would eliminate that switch statement as it feels not right. Ideas? 2008/7/15 DB Trigger - A Friend Or A Foe?Database triggers are useful, and I am not going to bush it completely. In some cases, like the one I run into, triggers are more of a distraction and source of issues, rather than help and ease of headache-free maintenance. In order to understand the case, players must be introduced first.
Some business process rules around the user inputs are defined as well
What is the standard DB approach and the simplest one to implement - put triggers in place. This is an absolutely valid approach. You observe the Inputs table, on updates to that table you trigger updates to the Metadata table and voilà it's working. Each time user makes an update to inputs, metadata is wiped and job is forced to re-run the calculations when it kicks in. Simple, elegant, but non trivial down the road. The application evolves, you add down the road more inputs and suddenly - the magic of triggers is done. You validate the fact that they are in place, but it is very easy to skip the fact that within the trigger the newly added fields to the inputs table are not processed. Another scenario - you want to be able to test the code, and see that changes to the inputs are actually triggering the metadata changes. But how would you do it, unless running in a debug with a real database attached? One idea is to remove the triggers from the database and implement them in the code, after all it is really an application behaviour we are trying to capture and express. Depending on how data access is implemented, the way to implement the code differs. We still are using home grown sort-of entities framework (hopefully not for long), and inputs table has a reflection in the application as an object of it's own. Initial idea was to create a proxy and it would update the metadata once inputs are persisted. Due to technical limitations of the framework we are using, the implementation went in a different technical route, but still, allowed to remove triggers and have it expressed as code, that can be tested and refactored. What were the goal of this exercise?
2008/7/12 How Did I Get Started In Software Development?JP called me out, so here I am, trying to travel back in time to recall how the heck I ended up coding. How old were you when you first started in programming? I was 16 years old when I touched computer for the first time with intension of more than just playing a video game. Not that I was a big gamer before that - I was not. I got my 1st computer when I was 16. How did you get started in programming? By accident, accident that took place at school. I had a subject called "Programming", and was doing well on everything, besides this one. My mom met the teacher who advised to purchase a computer for me, so I could practice more and get better grades in programming. So my mom did. Anticipated results were confirmed, partially. I improved my marks on programming significantly, abandoning the rest of the subjects behind. Got myself into programing graphics, demos, 3D. And that probably what can be tagged as the source of the decease. What was your first programming language? Pascal. What was the first real program you wrote? A demo for the local demo competition my friend and I put together. This was the first time I actually exposed what I was into publicly, letting the word "geek" stick to my first name, partially replacing the last name. What languages have you used since you started programming? Hebrew, English, Russian... ah, programming languages! Pascal, Delphi, C, C++, Java, JavaScript, DHTML, Assembly x86, T-SQL, VB6, VB.NET, C#. What was your first professional programming gig? Back in 2001, putting together the theory and the practicality at my first workplace. If you knew then what you know now, would you have started programming? Yes, yes, and yes. I would not change a thing in a way it started (at what age and circumstances), but I would definitely pay more attention to the wise advices I disposed through so many years, especially in the begriming. If there is one thing you learned along the way that you would tell new developers, what would it be? Stick to what you believe into, go forward to achieve your targets, but be careful not to become an ego-bully. Perfect yourself as a developer, knowing that perfect does not exist in nature, and yet not loosing your dream of doing better then you've done so far. Remember that as developers we use computers to accomplish our mission, but worked, working, and will work with real human beings, that are not computers. What's the most fun you've ever had programming? Being able to affect other developers. The power of satisfaction from being able to positively affect other developers around, constantly improve myself to allow surrounding do the same - is amazing. Finding friends that are also developers and as crazy about things that we do as I am, is the most fun, knowing that you are not the only one, and if locked up in an institution, you will your good friends with ya. Who am I calling out? 2008/7/3 Visual Studio Floating Windows and Keyboard ShortcutsVisual Studio floating windows are not playing nicely with keyboard shortcuts. If you only using a keyboard, and want to be able to navigate around without constantly getting destructed by a mice, it's ok. But floating windows are impossible to close without touching a mouse. At least I am not familiar with a way to do it. Imagine you could do something like this - press ALT-Space on floating window and... close! Wouldn't it be nice? :) 2008/7/2 MbUnit 2.4 PlugIn for ReSharper 4.0Albert Weinert has release an update to his plugin to allow execution of R# 4.0 from within Visual Studio .NET 2005 / 2008. The only Unit Testing framework supported by R# 4.0 out of box is NUnit, so this is a useful plugin for those who prefer MbUnit to NUnit. I recall that one of the final features for R# 4.0 was planned to provide out-of-box plugins and support for several Unit Testing frameworks. Wonder what happened to that plan. |
|
|