This article is my reflection and experience on the evolution and reconstruction of a small functional point in a project.
Background:
This project is an e-commerce website with the function of sending notifications to customers after the order status changes to a certain status, the SMS and gateway functions have been encapsulated into a set-up method. You can call them directly.
In order to clearly describe the functions related to this topicProgramDescriptionCode.
Reconstruction process:
At first, like in most projects, we worked hard to complete related functions within the specified time. The main code for this function is as follows:
V1
Static Void Sendsms_v1 (datatable DT ){ If ( Null = DT) Return ; For (Int I = 0 ; I <DT. Rows. Count; I ++ ){ VaR Row = DT. Rows [I]; orderstateenum state = (Orderstateenum )(( Int ) Row [ " Orderstate " ]); String Template = String . Empty; Switch (State ){ Case Orderstateenum. Unconfirmed: Template = " Dear {0! Your order has been successfully placed. Please pay as soon as possible for delivery. " ; Break ; Case Orderstateenum. Confirmed: Template = " Dear {0! Your order (Order No.: {1}) has been confirmed. Please wait. " ; Break ; Case Orderstateenum. Cancel: Template = " Dear {0! Your order (Order No.: {1}) has been canceled. Please check it online for the specific reason. " ; Break ; Case Orderstateenum. Finish: Template = " Dear {0! Your order (Order No.: {1}) has been completed. Thank you for your support and cooperation. Welcome to visit again. " ; Break ; Default : Break ;} String Content = String . Format (template, row [" Customername " ], Row [ " Orderid " ]); Sendsms. Send (row [ " Phone " ]. Tostring (), content );}}
This code works well at the initial stage of project launch.
After a period of operation, my colleagues in the Operation Department proposed to add the product name in some places. During this change, I found that the code was not separated from the data, in addition, if you want to add an SMS prompt or cancel a text message prompt after adding an order status, this change process is a bit troublesome. Therefore, I initially thought of putting the text message content in the configuration file, and reading the configuration file corresponding to the order status during the call and then formatting it. If the read content is null or the file does not exist, skip this step. The main code is as follows:
V2 content and data separation
# Region V2 content and data separation Static Void Sendsms_v2 (datatable DT ){ If ( Null = DT) Return ; For ( Int I = 0 ; I <DT. Rows. Count; I ++ ){ VaR Row = DT. Rows [I]; String Path = path. Combine (appdomain. currentdomain. basedirectory, " Smstemplates " , " V2 " , Row [ " Orderstate " ]. Tostring () + " . Txt " ); VaR Template = Filehelper. Read (PATH ); If (! String . Isnullorempty (Template )){ String Content = String . Format (template, row [ " Customername " ], Row [ " Orderid " ], Row [ " Productname " ]); Sendsms. Send (row [ " Phone " ]. Tostring (), content );}}} # Endregion
Template
Dear {0}, Hello! Your order (Order No :{1}) Confirmed. Please wait.
As the operation continues, the Operation Department constantly proposes to display some fields in some places, String. the number of parameters after the format is constantly increased. After each change, we need to re-test the entire process. This is a headache! My idea is to provide a data tag list on the development side, and provide an operation interface on the background to allow the operation colleagues to modify the SMS management template themselves, so they don't need to contact us every time? With the above idea, use a custom formatter:
Custom parameter formatting
Public Class Indexernamedformatter: iformatprovider, icustomformatter { Public Indexernamedformatter (){} Public Object Getformat (type formattype ){ If (Formattype =Typeof (Icustomformatter )) Return This ; Throw New Typeaccessexception ( " The type does not match. " );} Public String Format ( String Format, Object Arg, iformatprovider formatprovider ){ If ( Null = Arg) Throw New Argumentnullexception ( " The ARG parameter cannot be null. " ); Int Indexer = 0 ; Bool Isindexed =Int . Tryparse (format, Out Indexer ); // If it is datarow If (ARG Is System. Data. datarow ){ Return Getstringfromdatarow (format, ARG, indexer, isindexed );} // Such as datareader If (ARG Is System. Data. idatarecord) {getstringfromidatarecord (format, ARG, indexer, isindexed );} Return String . Empty ;;} Private Static Void Getstringfromidatarecord ( String Format, Object Arg, Int Indexer, Bool Isindexed ){ VaR Dr = (System. Data. idatarecord) ARG; String . Format ( " {0} " , Isindexed? Dr [indexer]: Dr [format]);} String Getstringfromdatarow ( String Format, Object Arg, Int Indexer, Bool Isindexed ){ VaR Row = (System. Data. datarow) ARG; Return String . Format ( " {0} " , Isindexed? Row [indexer]: Row [format]);}
Use custom tags in V3
# Region Use custom tags in V3 Static Void Sendsms_v3 (datatable DT ){ If ( Null = DT) Return ; Indexernamedformatter formatter = New Indexernamedformatter (); For ( Int I = 0 ; I <DT. Rows. Count; I ++ ){ VaR Row = DT. Rows [I]; String Path = path. Combine (appdomain. currentdomain. basedirectory, " Smstemplates " , " V3 " , Row [ " Orderstate " ]. Tostring () + " . Txt " ); VaR Template =Filehelper. Read (PATH ); If (! String . Isnullorempty (Template )){ String Content = String . Format (formatter, template, row); sendsms. Send (row [ " Phone " ]. Tostring (), content );}}} # Endregion
Template
Dear {0: Customername}, hello! Your order (Order No :{0: Orderid}, product: Product {0: Productname}) confirmed. Please wait.
After retrieving the data fields and writing them into a tag list document for the Operation staff, the modification requirements for this section will be quiet.
The above is just a small design technique, which also makes me understand how important it is to accurately grasp and mine requirements! Customers often say this today, but tomorrow, many people complain: You are in trouble! However, when the demand constantly arises, do we also reflect on the changes? Are we failing to accurately grasp the functions they want to design some problems? Of course, I do not advocate design at the very beginning. Excessive design requires a lot of time costs and may increase the complexity of implementation.
One question:
After reading the documentation, I have always felt that the implementation of Arg is system. Data. datarow is awkward. Why is the indexer not defined as an interface? Please tell us if anyone has a better implementation method. Thank you!
Thank you for reading this article! The Code involved in this article can be downloaded from here.