• 简读clubof网站源码之后的思考


         注:本文所阅读的clubof源码版本为FrienDevSourceCode_20081028,即2008年10月28日。

         按说昨天刚参加“微软技术创新日--北京站”活动之后, 今天就来评论其活动中产品的一些问题显
    得不太厚道。但本文内容绝不应当看作是关于clubof的负面评论,并且可以说我是绝对支持这个网站
    并响应其所“开放源码”这一举措的。甚至我还把该网站的宣传贴纸贴到了我的笔记本上。

         因为这是国内第一个开源并已上线动作的基于.net的SNS软件产品,更是先后有10名微软工程师
    耗时三个月心血所开发出的产品。所以光就这一点来说,在国内的.net开发者中就应该会很有影响力,
    甚至可能说是“从某种意义上传达出了微软内部开发者的一些声音”。

         报着这样的心态,我下载并开始阅读其源码,只不过是在阅读源码的过程中看到了一些有意思的
    问题,其中还伴随着自己的一些思考。这里再次重申一下,就是我希望看到该产品发展的越来越好,
    从而给国内的开发者竖立一个优秀的榜样。

        好了,下面就开始看一下我从源码中找出的一些问题。


       1.代码重复:
       
         位于FrienDevWeb项目下的"/Club/ActivityList.ascx"

         位于FrienDevWeb项目下的"/Club/ClubPostsList.ascx"

         重复代码段如下:
         
      
    #region Protected Method
        
    protected int GetQueryStringValue(string QueryName, int nullValue)
        {

            
    if (string.IsNullOrEmpty(this.Page.Request.QueryString[QueryName]))
            {
                
    return nullValue;
            }
            
    return Convert.ToInt32(this.Page.Request.QueryString[QueryName]);
        }
        
    protected string GetQueryStringValue(string QueryName, string nullValue)
        {

            
    if (string.IsNullOrEmpty(this.Page.Request.QueryString[QueryName]))
            {
                
    return nullValue;
            }
            
    return this.Page.Request.QueryString[QueryName];
        }

        
    protected bool GetQueryStringValue(string QueryName, bool nullValue)
        {
            
    if (string.IsNullOrEmpty(this.Page.Request.QueryString[QueryName]))
            {
                
    return nullValue;
            }
            
    return Convert.ToBoolean(this.Page.Request.QueryString[QueryName]);
        }
        
    #endregion
        
         建议:将这类代码重构到一个工具类当中,并以静态方法声明以方便调用。
         

       2.在ascx为后缀的cs文件中,存在类(实体)信息定义,比如:
       
         FrienDevWeb/Club/Activity/FeedBackSummary.ascx.cs
         
        
    class CustomQuestionAnswers
    {
        
    public string UserHomeUrl { getset; }
        
    public string Name { getset; }
        
    public string Answer { getset; }
        
    public string Question { getset; }
    }

       
        建议:可以新创建一个文件夹甚至项目来统一管理这种类实体信息。
        

       3. 业务逻辑被放置到了页面Page_Load之中,比如:
       
       FrienDevWeb/Club/Activity/FeedBackSummary.ascx.cs:


    protected void Page_Load(object sender, EventArgs e)
    {
        
    string activityIdString = Request["activityId"];
        
    if (!int.TryParse(activityIdString, out m_activityId))
            Response.Redirect(
    "~/Home/Default.aspx");
        
    if (!ActivityRules.Instance.IsCurrentUserIsActivityPromoter(m_activityId))
            Response.Redirect(
    "~/Home/Default.aspx");

        
    //下面是业务逻辑代码
        IEnumerable<User> users = ActivityRules.Instance.GetActivityJoined(m_activityId, -1);
        List
    <string> userIds = new List<string>();
        
    foreach (User user in users)
        {
            userIds.Add(user.UserId);
        }
        List
    <int[]> marks = ActivityRules.Instance.GetUsersFeedBackMark(m_activityId, userIds);
        
    int markCount = marks.Count != 0 ? marks.Count : 1;
        
    int overallSum = 0;
        
    int contentSum = 0;
        
    int organizeSum = 0;
        
    foreach (int[] mark in marks)
        {
            overallSum 
    += mark[0];
            contentSum 
    += mark[1];
            organizeSum 
    += mark[2];
        }
        
    //业务逻辑代码结束
        lblOverallMark.Text = overallSum.ToString();
        lblContentMark.Text 
    = contentSum.ToString();
        lblOrganizeMark.Text 
    = organizeSum.ToString();
        lblOverallAverage.Text 
    = (overallSum / markCount).ToString();
        lblContentAverage.Text 
    = (contentSum / markCount).ToString();
        lblOrganizeAverage.Text 
    = (organizeSum / markCount).ToString();
       
    }

        
        上面代码段中的“IEnumerable<User> users = ActivityRules.Instance...” 开始,到foreach循环结束,
    我感觉这块代码应该被放在业务逻辑层中,而Page_Load方法要做的事就是获取表单数据和相应控件的信息绑定
    即可,如果像这样把业务逻辑代码强加到当前方法后,会造成当前page_load的(业务)流程代码过多,造成代码阅
    读的困难。

           
       4. 方法定义后却未被使用,怀疑是“代码拷贝”的恶果,参见:ClubInviteService.cs中有对上面的
    GetQueryStringValue等私有方法的声明。


       5. WebService中的实体类声明倒底该不该放到相应的服务类定义中,我想这个问题大家可能会有不同意见,
    但我的想法是提出来放到一个公共的entity或信息类项目中,这样会更清楚,不是吗?

        出现这个情况的是FrienDevWeb/App_Code/SelectFriendsService.cs,代码如下:

    //注:代码上方为SelectFriendsService类的定义
    public class UserItem
    {
        
    public UserItem()
        { }

        
    public UserItem(string UserId, string FullName, string PhotoFileName)
        {
            
    this.UserId = UserId;
            
    this.FullName = FullName;
            
    this.PhotoFileName = PhotoFileName;
        }

        
    private string m_UserId;
        
    public string UserId
        {
            ..
        }

        
    private string m_FullName;
        
    public string FullName
        {
           ..
        }

        
    private string m_PhotoFileName;
        
    public string PhotoFileName
        {
            ..
        }
    }



        当然这几个小问题不会影响系统的稳定性或性能,只不过是造成代码阅读和项目分布理解上的困扰。

        下面就到了我所认为的分层问题了,其实看过MVCStore_Preview1A源码的人可能会发现一些情况,
    就是其对Service的引入和使用,这些Service是封装的是业务逻辑,这类逻辑的数据访问代码并不是直
    接访问LINQ设计器所生成的类或方法,而是又封装了一层,而这一层的封装有统一的命名规范,即添加
    后缀“Repository”,这类做的好处应该是将频繁使用的数据访问方法先抽出来加以封装,比如说订单
    等,而Service层会对这类频繁访问的代码直接使用,以确保Service只加关心业务逻辑层的实现,我想
    MVCStore的做法是可以认真考虑并加以参考的。比如说:

    /// 位于MVCStore_Preview1A代码的SqlOrderRepository.cs中
    /// <summary>
    /// Returns a current, unchecked-out order for the current user
    /// </summary>
    public Order GetCurrentOrder(string userName)
    {
        Order result
    = _orderRepository.GetOrders().CurrentOrderForUser(userName).SingleOrDefault();

        
    if(result==null)
        {
            
    //create a new one
            result=new Order(userName);
            
        }
        
    return result;
        
    }

    //Commerce.Data.SqlCatalogRepository
    public IQueryable<Order> GetOrders() {

        var orders 
    = from o in _db.Orders
                     let items 
    = GetOrderItems(o.OrderID)
                     let transactions 
    = GetTransactions(o.OrderID)
                     select 
    new Order
                                {
                                    Status 
    = (OrderStatus)o.OrderStatusID,
                                    DateCreated 
    = o.CreatedOn,
                                    ID 
    = o.OrderID,
                                    OrderNumber 
    = o.OrderNumber,
                                    Items 
    = new LazyList<OrderItem>(items),
                                    Transactions 
    = new LazyList<Transaction>(transactions),
                                    ShippingAddress 
    = GetAddresses().Where(x => x.ID == o.ShippingAddressID).SingleOrDefault(),
                                    BillingAddress 
    = GetAddresses().Where(x => x.ID == o.BillingAddressID).SingleOrDefault(),
                                    ShippingMethod 
    =GetOrderShippingMethod(o.ShippingMethod, o),
                                    UserName 
    = o.UserName,
                                    UserLanguageCode
    =o.UserLanguageCode,
                                    DateShipped
    =o.DateShipped,
                                    EstimatedDelivery 
    = o.EstimatedDelivery,
                                    TrackingNumber
    =o.TrackingNumber,
                                    TaxAmount
    =o.TaxAmount,
                                    DiscountReason
    =o.DiscountReason,
                                    DiscountAmount
    =o.DiscountAmount
                                    
                                };
        
    return orders;

    }

    ///该方法被放在了Commerce.Data.OrderFilters中,但我感觉这块代码应与上面代码放在一起
    public  static IQueryable<Order> CurrentOrderForUser(this IQueryable<Order> qry, string userName)
    {
         
    return from o in qry
                
    where o.UserName == userName && o.Status==OrderStatus.NotCheckoutOut
                select o;
       
    }

         当然MVCStore这种数据获取方式有个问题,就是效率,即_orderRepository.GetOrders()先是返回所有定单,
    而不是仅获取符合条件的数据,并且在后续方法中访问CurrentOrderForUser方法来传入条件 。

         注:当然这个判断可能会有误差,就是LINQ中如果不使用tolist这类方法时,查询是不会被马上执行的,这个技
    术好像叫lazyload,如果上面两个方法(GetOrders方法和CurrentOrderForUser方法)的调用只是在将linq语法转
    (翻)译成sql语法的话,上面的观点就不成立了,因为在Service中代码的最后会调用SingleOrDefault(),如下


        _orderRepository.GetOrders().CurrentOrderForUser(userName).SingleOrDefault();

        也就是在该方法(SingleOrDefault)调用时,才会真正将转义过来的sql语句加以执行(向数据库递交查询请求)。
        
        当然这也是个猜测,如果有对LINQ研究的比较深入的朋友可以测试一下,然后告之一下我,以免我的判断出
    现误差,在此先行道谢了。


        下面再看看在业务规则层中的一个小问题:)

        有关于单体(singleton)模式的实现方法,我看到了这样的代码(位于FriendRules):


        
    public static FriendRules Instance = new FriendRules();

        
    private FrienDevDataContext m_DataContext = FrienDevDataContext.Instance;

        
    private FriendRules()
        {
        }
    ..



        然而在UserRules.cs文件中又出现了下面这种单体模式的“经典实现”代码:   
       
        private static UserRules _instance;

        
    public static UserRules Instance
        {
            
    get
            {
                
    if (_instance == null)
                {
                    _instance 
    = new UserRules();
                }
                
    return _instance;
            }
        }


        看来在如何实现单体模式上,团队的开发者中还是有不同意见和编写方式的:)
        
        不过如果使用下面这种方式的话,可以说会造成一个并发访问的问题,而这个问题以前在网上就有
    人提出过并给出了一个方案,这里就不再多说了。

        建议将上面的UserRules代码修改成:
        
       
        public static UserRules Instance
        {
            
    get
            {
                
    if (_instance == null)
                {
                    
    lock (lockHelper)
                    {
                        
    if (_instance == null)
                        {
                            _instance 
    = new UserRules();
                        }
                    }
                }
                
    return _instance;
            }
        }
        
        好了,限于尚处于刚开始阅读clubof代码,对该项目的代码还远远谈不上了解,今天的内容就先到这里了。
        

        最后希望国内.net开发的所有软件产品越做越好,市场越来越大。

         原文链接:http://www.cnblogs.com/daizhj/archive/2009/03/04/1402708.html

         作者: daizhj, 代震军

         Tags: clubof,source code, 源代码

         网址: http://daizhj.cnblogs.com/

       

       
  • 相关阅读:
    《软件工艺》-初评
    UI设计心得(1)
    理想生活应该是...
    OpenPOP.NET+OpenSMTP.NET=?最强.NET开源邮件组件 Mail.NET!
    Outlook应用开发(3)之插件引擎
    最近发现的几个酷.net代码
    买了几本新书,推荐一下
    一个游标简单例子
    winform中捕获程序未处理的所有异常
    DataTable的Merge方法和添加datatable到dataset
  • 原文地址:https://www.cnblogs.com/daizhj/p/1402708.html
Copyright © 2020-2023  润新知