重构经历(一)
一、基本需求
在当前的项目中,很多用户都有订阅论坛中不同的类别,程序需要根据用户订阅的触发时间来向用户发送邮件。
在这里涉及到几个表。
WebSites包含论坛的URL, LogoURL, Status, Description等信息
Campaigns 和WebSite相关,一般情况下一个WebSite只对应一个Campaign,它也包含一个Status
Subscribers 订阅用户,包含Email, FirstName, LastName等订户信息
Subscriptions 订阅用户所订阅的类别,包含类别,类别所在的论坛,订阅时间,接收邮件的具体时间(时分秒),接收周期(WeekInterval varchar(7) 值可能是0123456这个字符串中的任意组合,如023表示用户将在周日,周三,周四收到邮件)。
实际上Subscribers和Subscriptions也都包含Status字段,有些信息是不能够通过邮件来发送的或者用户退订了。
二、实现过程
之前由于不太明确需求,我把业务逻辑的主次给颠倒了,我是根据当前时间去查询Subscriptions表,看有哪些订阅者,这些订阅者又分别属于哪个WebSite,这是一个从下往上找的过程。
Subscriptions –> Subscribers –> Campaigns / WebSites
但是后来醒悟过来,如果WebSite的Status为"D"(Disabled),那么这个站点下的所有用户将在一段时间内收不到邮件,知道Status转变为"A"(Active)。
现在转变过来了,则应当是:WebSites –> Campaigns –> Subscribers –> Subscriptions。
之前的WebService部分和存储过程在现在这种情况下都要改动。
三、重构过程
(1) 第一次重构
之前为了测试方便,把方法分的很开,可笑的对Campaign写了3个WebMethod,GetCampaign()、IsCampaignExists()、CreateCampaign(),甚至每个方法都会调用一个简短的StoredProcedure。其实我的目的仅仅是为了获取一个Campaign而已,至于IsCampaignExists()、CreateCampaign()这根本和功能无关紧要。于是我考虑三个存储过程合而为一,三个WebMethod也合而为一。改成GetCampaign()
于是,我改了方法,改了存储过程,自以为代码量减少了,业务逻辑也符合了,但是却忽略了Campaign和WebSite的Status
(2)第二次重构
由于加入了Status,所以至上而下的找,有缝缝补补,更为GetActiveCampaign()
(3)第三次重构
到现在我才真正明白这个需求,我其实是为了拿取当前时间下,状态处于Active,并且有订阅用户将会接收到邮件的WebSite,其他的Campaign并不是最顶层的。
而且第二次重构的结果导致我只能拿取一条数据,这样则会造成多次访问数据库,我还是索性的一次拿光吧。最终只有GetActiveWebSites()
四、SQL重构
(1)第一次重构
获取当前有订户需要接收邮件的WebSite
create proc [dbo].[SP_GetCurrentWebsites] ( @weekday int ) as declare @now datetime declare @now_time time set @now = GETDATE() set @now_time = @now select distinct Subscribers.WebSiteID from Subscribers inner join Subscriptions on Subscribers.ID = Subscriptions.SubscriberID where WeekDays = @weekday and StartTime < @now_time GO
获取处于激活状态的WebSite
create proc [dbo].[SP_GetActiveCampaigns] as select Campaigns.* from Campaigns inner join WebSites on Campaigns.WebSiteID = WebSites.ID and WebSites.Status = 'A' and Campaigns.Status = 'A' GO
然后获取最终的WebSite的方法我是放到程序里进行处理的,写好了,发现这让我感觉很别扭,总觉得多余,为了获取WebSite我竟然花了3个步骤。
(2)第二次重构
简化为一个存储过程,一个WebMethod调用。
--获取WebSite create proc SP_GetActiveWebSites ( @weekday varchar(1) ) as declare @now datetime declare @now_time time set @now = GETDATE() set @now_time = @now select w.ID from ( select distinct s1.WebSiteID from Subscribers s1 inner join Subscriptions s2 on s1.ID = s2.SubscriberID where CHARINDEX(@weekday,s2.WeekDays) > 0 and StartTime < @now_time ) t inner join WebSites w on t.WebSiteID = w.ID inner join Campaigns c on c.WebSiteID = w.ID where w.Status = 'A' and c.Status = 'A' go --执行查询 declare @start datetime set @start = GETDATE() exec SP_GetActiveWebSites '1' select DATEDIFF(MILLISECOND, @start, GETDATE()) as QueryTime
为了获取几条数据,这个查询花了4000多毫秒,由于Subscriptions和Subscribers表数据量较大,这几个表都inner join会花费很多时间。
(3)第三次重构
alter proc SP_GetActiveWebSites ( @weekday varchar(1) ) as declare @now datetime declare @now_time time set @now = GETDATE() set @now_time = @now --临时表1 create table #Temp1(ID bigint, WebSiteID bigint) insert into #Temp1 select Campaigns.ID, Campaigns.WebSiteID from (Campaigns inner join WebSites on Campaigns.WebSiteID = WebSites.ID and WebSites.Status = 'A' and Campaigns.Status = 'A' ) --临时表2 create table #Temp2(WebSiteID bigint) insert into #Temp2 select distinct s1.WebSiteID from Subscribers s1 inner join Subscriptions s2 on s1.ID = s2.SubscriberID where WeekDays = @weekday and StartTime < @now_time --临时表1,表二联合查询 select t1.ID, t1.WebSiteID from #Temp1 t1 inner join #Temp2 t2 on t1.WebSiteID = t2.WebSiteID go --执行查询 declare @start datetime set @start = GETDATE() exec SP_GetActiveWebSites '1' select DATEDIFF(MS,@start,GETDATE())最终这个查询只需要500毫秒左右,重构完成。