注:本文所阅读的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 {
get
;
set
; }
public
string
Name {
get
;
set
; }
public
string
Answer {
get
;
set
; }
public
string
Question {
get
;
set
; }
}
建议:
可以新创建一个文件夹甚至项目来统一管理这种类实体信息。
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/