我希望能够编写优美的代码。! ~8 g+ A! ~# |. y) z# @# x( u
优美的代码就像一篇散文,易懂易读,而且看起来很漂亮。在《代码之美》一书中,收录了Ruby之父松本行宏的一篇文章,名为《把代码当作文章》,大约表达了同样的含义。Thoughtworks的一位工程师在《软件开发沉思录》一书中提出,每个类的方法最好不要超过5行。最初让我感觉很惊诧,继而觉得不可能。虽然这位工程师言之凿凿,提到在自己参与的项目中,所有代码都完全遵循了这一规范,我仍然表示怀疑。最近,阅读了Robert C. Martin的著作《代码整洁之道》(英文版名为Clean Code),看到Uncle Bob演示的代码,真是漂亮极了。仔细一看,这些好的代码在每个方法中大多数都没有超过5行。诀窍在哪里?那就是重构手法中最常用的Extract Method。进一步讲,如果我们能够为每个类与方法以及变量定义出好的名字,代码确实可以变成一篇散文。当然,是英文散文。8 k# A8 q. V/ v/ g1 o' j q3 \
今天,我在重温.NET的序列化时,在MSDN上找到一篇演示Xml序列化的示范代码。或许是因为示范代码的缘故,这一段代码写得极其地不优雅,甚至显得有些丑陋:public class Test {" A0 `/ ]0 f: g" K
public static void Main() {' ~; H; |; s- }8 @, c) _
// Read and write purchase orders. ( h" t. |; T. f7 L1 l Test t = new Test();/ G H$ l* H" C1 m
t.CreatePO("po.xml");' F. a# Z2 B$ e% A g2 Y# ?
t.ReadPO("po.xml"); & X" V. i" o! _8 W( ?) _$ e5 Z. S } 6 h3 t6 q, R- _2 o7 R+ | z9 y0 W( B3 w ! I( N* a8 _9 x/ p, V( k private void CreatePO(string filename) {7 i, g+ ^2 A L& }- g2 ~! V% T
// Create an instance of the XmlSerializer class;# W7 s1 c, i% L- u) o% M, y
// specify the type of object to serialize. 0 ~/ G9 R r) c v4 ~ XmlSerializer serializer =; l' c9 {* E: s- T: M+ ]
new XmlSerializer(typeof(PurchaseOrder));" O) L5 I) M) p* m& J* Z- L
TextWriter writer = new StreamWriter(filename); % l4 | {/ a: v# ~ PurchaseOrder po = new PurchaseOrder();( o+ }: G/ `. ?- l* F: ]* C% A
3 a6 q% O1 |/ [1 L* @ // Create an address to ship and bill to.6 p: K$ d. y0 v, f3 D
Address billAddress = new Address();1 A2 Y7 E; X; a+ r9 V8 P
billAddress.Name = "Teresa Atkinson";$ }5 s; G% t' c/ I( e4 c% [
billAddress.Line1 = "1 Main St."; # Q6 V2 J5 @- Z/ l$ S! @5 } billAddress.City = "AnyTown"; 8 f$ a% C. b3 M9 m4 k+ {; l7 q billAddress.State = "WA";" p; R) x0 b0 W8 V
billAddress.Zip = "00000"; 3 ~5 [& K( ]6 x. O- G2 h // Set ShipTo and BillTo to the same addressee. / b* o! ]1 s: M# [3 \4 U3 X: m) I po.ShipTo = billAddress;; L7 ^2 S, h7 d& _
po.OrderDate = System.DateTime.Now.ToLongDateString();' K7 x7 @8 x$ g( z4 B$ ^- T
4 |/ Z. I) B/ X // Create an OrderedItem object.# o4 H$ W& E2 Z" k& X) m/ V: }
OrderedItem i1 = new OrderedItem();$ @ N( B# h/ N) i5 L7 b/ E3 U
i1.ItemName = "Widget S"; # I* S7 u( e( l i1.Description = "Small widget"; W3 j2 J4 ]1 q( Y9 K, q8 F
i1.UnitPrice = (decimal)5.23; / J1 h% X6 \! u9 Z i1.Quantity = 3; , s" Y3 r2 Z8 D y7 R, Y* N+ \ i1.Calculate(); ! h% ^6 \+ d7 S6 l+ f2 l v2 I; Y& Z8 s! I8 D) Z2 ]. w // Insert the item into the array.2 c d1 P$ G3 _9 W% ]
OrderedItem[] items = { i1 }; 4 [1 B) w$ ?4 u po.OrderedItems = items;* T3 Z. T1 c( W. u4 k7 G5 X# S
// Calculate the total cost.5 C3 ]) f: L" A
decimal subTotal = new decimal();/ N' P0 u/ q* ]8 g5 t5 H
foreach (OrderedItem oi in items) {" [1 ^" {/ P+ U$ k( f
subTotal += oi.LineTotal;, Q, l6 Q0 v3 ?$ |
} 5 I. V8 E) N: n6 e3 {8 \' ^ |' O5 A$ z po.SubTotal = subTotal; , r$ j4 k5 k; a* t% \# g po.ShipCost = (decimal)12.51;) H% P5 }* `7 F. B0 G$ B' `/ I
po.TotalCost = po.SubTotal + po.ShipCost; 1 m% R0 I4 I# O; `4 I6 ^ // Serialize the purchase order, and close the TextWriter.4 Y6 O {) ^! N' p" p
serializer.Serialize(writer, po); 3 B6 R- A9 [( _6 v, Y+ p writer.Close();. n' a, N0 c; m+ I
}1 i5 z2 O' \" f1 X- p& y) h
t% N _8 K2 k- P' t$ [) D$ q3 ^
protected void ReadPO(string filename) {+ [% Z8 H6 _& M/ w' Z2 V, Y: @
// Create an instance of the XmlSerializer class; " n/ e% q% y3 y; ^ // specify the type of object to be deserialized.9 x' s% E o! y; `# f9 j
XmlSerializer serializer = new XmlSerializer(typeof(PurchaseOrder));/ A- b* p9 u) i% H, R% l
/* If the XML document has been altered with unknown $ ~7 p7 \) B2 r' \/ q$ @) A nodes or attributes, handle them with the , H! u0 [; J8 I6 g5 n
UnknownNode and UnknownAttribute events.*/ 2 D3 S: C' C; \8 r, l serializer.UnknownNode += new8 t0 Q# s0 G9 B
XmlNodeEventHandler(serializer_UnknownNode); 6 s) z7 q( @* V& H' f. ^+ T( D serializer.UnknownAttribute += new; K, {; P8 X s" z
XmlAttributeEventHandler(serializer_UnknownAttribute); % S, I6 H5 B& Q; i " I @; D/ E' t' p4 M8 @! B // A FileStream is needed to read the XML document. 9 _, Y. X% a) x0 A FileStream fs = new FileStream(filename, FileMode.Open);3 g) b6 S0 E+ u; ~" |# m
// Declare an object variable of the type to be deserialized. : u7 D0 O; l/ ?5 ]- U6 ] PurchaseOrder po; $ B. z5 J- O7 D /* Use the Deserialize method to restore the object's state with $ z8 q m7 y1 H' A+ d/ _ data from the XML document. */, W& R6 o2 U* N+ e T; R; M
po = (PurchaseOrder)serializer.Deserialize(fs);5 [6 ]3 W1 _2 H& T
// Read the order date.; G0 U* r& o l
Console.WriteLine("OrderDate: " + po.OrderDate); * f9 S L# k1 q$ V; z/ q& X& Q9 a4 i4 ^
// Read the shipping address. % X! I5 M Q7 l6 _' H# w% m5 E2 p' u Address shipTo = po.ShipTo; ; D* q: X1 z. S9 a8 }! | ReadAddress(shipTo, "Ship To:"); ; M, a( m5 N$ v& z7 ]+ S+ j. P; N' ] // Read the list of ordered items.4 u5 |, M+ o! T7 a- {
OrderedItem[] items = po.OrderedItems;6 Z u& n! c3 g" H0 I, i* M
Console.WriteLine("Items to be shipped:");/ \- B. H" l' K6 i: I
foreach (OrderedItem oi in items) { # ]: a4 I3 ?( y. A. j Console.WriteLine("\t" ++ F& f2 G& j9 w4 O G
oi.ItemName + "\t" + - _1 x7 F* K4 k' N& X oi.Description + "\t" + ; j B& J* O1 ~0 d7 C* O oi.UnitPrice + "\t" +4 G- t! E+ V; u2 n# B2 g5 P* \* {* l
oi.Quantity + "\t" + ) `! C% b8 H# K/ y& h8 E oi.LineTotal);7 q; w9 Y/ V' Y, y7 E
} 4 X" V% K: v/ t, C // Read the subtotal, shipping cost, and total cost.9 W6 l: ^4 E, ^1 S0 S5 h( `
Console.WriteLine("\t\t\t\t\t Subtotal\t" + po.SubTotal);1 F+ C M) j+ i0 n' Z( B9 t# r
Console.WriteLine("\t\t\t\t\t Shipping\t" + po.ShipCost);/ c4 c6 I6 m+ y# {
Console.WriteLine("\t\t\t\t\t Total\t\t" + po.TotalCost); 0 d% e1 B+ C) [, r } , d' x5 f' q7 J. Q+ s! R8 R5 M/ x6 d2 t; t8 x* ]: J6 k7 J3 d* d1 R
protected void ReadAddress(Address a, string label) { : e2 h' A4 } I0 c. A9 ? // Read the fields of the Address object. ; k! g% K8 [1 ^9 Q3 s# u Console.WriteLine(label); 8 w) G5 p% o3 z9 J6 {+ a: V Console.WriteLine("\t" + a.Name);) O( q9 b/ L4 S3 w$ R: H
Console.WriteLine("\t" + a.Line1);) Z/ h6 P5 f, p
Console.WriteLine("\t" + a.City); % t$ A5 u" P) ^8 m0 G6 s: Z: @ Console.WriteLine("\t" + a.State);/ j3 o" e( W$ m* s, {/ c
Console.WriteLine("\t" + a.Zip);4 N0 i1 d; F! r; r+ c. Q
Console.WriteLine();# e k. e) G4 h1 m5 z
} $ Q: t' f. o% e1 q+ n+ J5 s 9 w$ f- M; J1 c% N, w+ W- C private void serializer_UnknownNode- v1 e5 o) l1 w' Q k( R2 P2 _) Q# L
(object sender, XmlNodeEventArgs e) {% q* W. u" E6 @: O0 E
Console.WriteLine("Unknown Node:" + e.Name + "\t" + e.Text);' @. h$ S' C# P c8 ]
}3 |6 y4 @3 {7 {4 C# x. d& o! `
* V' R- l% V& e
private void serializer_UnknownAttribute- d' g/ F8 z! w U9 `# f
(object sender, XmlAttributeEventArgs e) {3 Z5 N4 q9 I5 t2 k
System.Xml.XmlAttribute attr = e.Attr;5 A0 L& D2 b/ [: V! [
Console.WriteLine("Unknown attribute " +7 I% m% L! R2 ~
attr.Name + "='" + attr.Value + "'");) U4 _! {# _+ x6 j
}# t- W% J7 i0 A: ], w& G3 g1 s3 S8 l
} ; k$ l+ a7 h+ l* m* i2 B9 K* U6 H. e 2 C, H% f" C* B4 M/ [. N+ c# |6 ~9 Q; `9 L
# X# {9 U% [) l8 Q% ?, r6 m
看看CreatePO()和ReadPO(),多么地冗长。虽然这个实现极为简单,但对于代码的阅读者而言,想要一下子抓住该方法的中心思想,仍然比较困难。此外,方法中的注释也显得多余,因为,代码本身就可以给予很好的说明。 ! f! r* B& m( ^下面,是我对这段代码的重构,大家可以对比对比,是否更加容易阅读呢? public static class PurchaseOrderHandler { . `! H: ?8 g# k public static void CreatePurchaseOrder(string filename) { 0 G% E) g! l6 m1 { H9 F3 b, X PurchaseOrder po = BuildPurchaseOrder(); 4 F' N7 f2 D3 F" t! m6 ~ XmlSerializer serializer = new XmlSerializer(typeof(PurchaseOrder)); ! f. u" g' }6 M using (var writer = new StreamWriter(filename)) { ; o2 T! _. N. J; D. W+ _. N7 D$ a serializer.Serialize(writer, po);. M' B) I$ i) {
} 6 U4 i' n: e- z4 I0 J& K' s2 H4 t5 @ }# o! H0 P2 ^: |/ ~' I: b6 F
& ?6 \5 l2 P% O3 G' s% M4 ^2 S' s
private static PurchaseOrder BuildPurchaseOrder() { - D6 @6 u6 i; v* u Address address = CreateAddress(); # ?8 T2 F/ Y: D, z! `- `) ? OrderedItem i1 = CreateOrderedItem();, z5 i) g4 l; T6 P: T
OrderedItem[] items = { i1 };: P0 ]& r1 Q' P: Y' Q
4 f* [( C5 C1 F! e$ D6 x
PurchaseOrder po = new PurchaseOrder(); " ?1 y+ k' N l1 N: Y po.ShipTo = address;6 W- M. ^ B+ R( U* y' H
po.OrderDate = System.DateTime.Now.ToLongDateString(); / D2 m c% D# t6 z po.OrderedItems = items; ' b/ B; ?9 A; s1 T" @ po.SubTotal = CalculateSubTotal(items); 6 |( v; z f' O po.ShipCost = (decimal)12.51; ! F7 }1 Y6 q. @& F po.TotalCost = po.SubTotal + po.ShipCost; 1 x5 R3 m0 h8 ^( @+ s3 c , t7 E, L% X" ^# @ return po;/ A( C4 z% B3 k1 i- ]" }% c6 q
} 6 B$ [( _+ ?( c7 j) d+ g& B ' t6 _+ d! o& {% {& |4 s private static decimal CalculateSubTotal(OrderedItem[] items) { ! E! v/ C5 C* g2 s9 t" t decimal subTotal = new decimal(); 7 u1 R3 w* X0 m+ W% |" L, d foreach (OrderedItem oi in items) { ~- C$ |# v6 {" v2 h$ @2 h4 H+ [ subTotal += oi.LineTotal;; X' @+ {/ m- c' Y9 n. C+ m: U
}4 @0 r8 Y0 A- e$ r, p
- [! e3 P4 g0 t' t6 e return subTotal;; U Z4 B2 q2 {3 a$ b
}! L; ?& z" Y$ x
$ M/ D$ u% ~$ K, x S
7 G+ `9 c8 [" Y! U1 h- F private static OrderedItem CreateOrderedItem() {7 |: i/ _! a2 l
OrderedItem i1 = new OrderedItem(); $ b/ e2 K: c. x- b, j, Z1 V i1.ItemName = "Widget S"; A1 P6 P. _8 \0 p6 e: \) j. D
i1.Description = "Small widget"; 7 S3 z/ i( r3 _# T i1.UnitPrice = (decimal)5.23;+ r8 \6 I+ L* K# b
i1.Quantity = 3;4 t. U: j) m D! l
i1.Calculate();$ x% f+ G, i* @
return i1; 5 K, K( G F+ [2 O# x } 6 X- r7 u1 W% q+ B; \9 U4 l+ F, f3 Y
private static Address CreateAddress() { 4 P5 Z+ o) N5 ~3 I( ^$ q7 A Address billAddress = new Address();& O3 F" m- n& B4 q2 s) e# M
billAddress.Name = "Bruce Zhang"; ) o, r8 r" [ T M8 f) I billAddress.Line1 = "1 Main St.";; z! l6 W7 w0 q e$ g6 O
billAddress.City = "Chong Qing";8 E% p1 ]' U$ ?( |
billAddress.State = "Chong Qing";6 C% l; g. b( u3 E
billAddress.Zip = "400000";1 b. ~8 f$ v0 h+ Q# T- H
. Y3 X' m7 E( \; O. n return billAddress;4 }4 O! R& e5 F$ {
}% Q2 U( E# I8 z
! [# g( n5 W: V+ u% C public static void ReadPurchaseOrder(string filename) {* l# V( Q% n5 U! e. `: R6 p& C) m
XmlSerializer serializer = new XmlSerializer(typeof(PurchaseOrder)); ; T8 i7 K3 }+ i# K + r4 |* p) m( T7 f0 T# }! f+ [ serializer.UnknownNode += new XmlNodeEventHandler(serializer_UnknownNode);) K- s# U: {1 r( B, ~' h2 F8 C
serializer.UnknownAttribute += new XmlAttributeEventHandler(serializer_UnknownAttribute); 0 U. g$ D1 H+ S " C) M9 v; i: V- n+ W, k6 u FileStream fs = new FileStream(filename, FileMode.Open);2 g# M5 G0 ?! j' c
5 u& \' k! c3 x8 ?9 J) r+ M P. t s
PurchaseOrder po;+ |& E( `& P$ ~: m1 s# A
po = (PurchaseOrder)serializer.Deserialize(fs);8 b* f; p5 L* J7 ^: ]
PurchaseOrderPrinter.PrintPurchaseOrder(po); ( \9 Y& O- U/ [- m- q- Q2 R& ] [ } ; I! f; u" @* |+ K3 f) ~$ w. B: c7 @& w, P' x
! R+ o0 Y" O+ _% ~, B" Y
private static void serializer_UnknownNode7 W; f! {6 Y3 {/ U) \ G
(object sender, XmlNodeEventArgs e) {0 z1 [7 _ C* ^' {* z2 R
Console.WriteLine("Unknown Node:" + e.Name + "\t" + e.Text); : {2 t" W0 G& @0 P& d } 3 e- g$ P! ~) r% ~3 m; D8 u6 j. X , Y7 T; r @ `" Y, k! x private static void serializer_UnknownAttribute! z1 s8 Q0 D i# t, y
(object sender, XmlAttributeEventArgs e) {& R+ t W( H, u# D3 E& }7 Q
System.Xml.XmlAttribute attr = e.Attr; 9 S1 E+ |/ S$ a$ \ Console.WriteLine("Unknown attribute " +$ ~+ G0 z- Q% H
attr.Name + "='" + attr.Value + "'"); h* V- ?7 ?- G3 O
} ; z8 |9 @* i. J- X4 A* G5 q3 m9 e 2 R2 w" \6 H+ c; A private static class PurchaseOrderPrinter {3 k( ^/ F6 B* Q+ m' U9 ~# u. u
public static void PrintPurchaseOrder(PurchaseOrder po) { ! h( G' K, f. l c6 G$ ~1 a8 O: r PrintOrderDate(po); . k& a# W- n% f2 q4 T& ?. u2 e5 ]" P PrintAddress(po.ShipTo);4 F0 ]) ?. \ e4 i/ K6 M- f, M
PrintOrderedItem(po.OrderedItems); % N% J+ s& R7 V/ W PrintOrderCost(po);& v8 G/ Z5 ~ {) l; V: b
} 3 u9 z$ `2 i1 ?2 \2 B2 x1 D+ O- d; p, w3 G- C
private static void PrintOrderCost(PurchaseOrder po) {6 R1 P& r' s- X8 M% X; O
Console.WriteLine("\t\t\t\t\t Subtotal\t" + po.SubTotal); ) v( P2 ]& [4 U7 W Console.WriteLine("\t\t\t\t\t Shipping\t" + po.ShipCost);% \$ q, I7 F* H- q+ ~' A( {$ ^6 c
Console.WriteLine("\t\t\t\t\t Total\t\t" + po.TotalCost); ! \ O- l1 s' ~, d: ^0 I1 Q5 C }( }4 d8 M& M7 k2 G4 l/ o
. m% f8 R; N3 A' N0 b0 }
private static void PrintOrderDate(PurchaseOrder po) { P* D9 X7 q' h6 n8 e Console.WriteLine("OrderDate: " + po.OrderDate); 0 R6 b: x0 P4 s# _6 ? }+ X9 {' e0 w& E: q+ s
8 P( r/ k+ c* k: V% i' A8 x private static void PrintOrderedItem(OrderedItem[] items) {) B- z- |! i( P- }. Z4 b
Console.WriteLine("Items to be shipped:");) f* q B1 T0 o
foreach (OrderedItem oi in items) { ! c/ M( H1 v6 U ` T% X Console.WriteLine("\t" + 6 P% T. @# C8 v' {6 \0 q5 p& N oi.ItemName + "\t" + 6 N2 W# x7 z( j* ~4 O. ~ oi.Description + "\t" + 3 c/ P+ z7 q1 O+ c/ n/ i/ T oi.UnitPrice + "\t" +0 p8 J5 l8 q; L# `' o% o x% S
oi.Quantity + "\t" +1 l3 ^/ j3 }0 G ^, }2 l- j% q* D
oi.LineTotal); R. g- `: L4 x" e* E7 ^$ I
} / Y3 ^3 x8 |+ l7 y) n }& R/ C3 r0 K) H; _0 z7 V
9 k9 r: L1 [( b+ |7 r) o6 |. C
private static void PrintAddress(Address a) {; L2 _/ Q+ d7 p7 D
// Read the fields of the Address object. + C8 v' w0 b: y1 k+ K; ^3 p7 [ Console.WriteLine("Ship To:"); * k7 ^( W3 v5 F2 z. h/ |+ t/ j Console.WriteLine("\t" + a.Name);4 z4 L/ m/ ~6 w6 N9 N) N
Console.WriteLine("\t" + a.Line1); % C, D6 [' d1 m! }. c. Y% A0 ] Console.WriteLine("\t" + a.City); # r5 m( [4 X$ o; b6 ~# i Console.WriteLine("\t" + a.State); * `( O6 H# x! G, ]# M Console.WriteLine("\t" + a.Zip); " a# R- K. Q9 S! \( K% \" {' {- b! V Console.WriteLine();. b& d* w7 F7 h8 C& P, J! j! m* A
} 4 m4 T$ \3 P l/ I( X" [+ ~: ?! h } + e9 U: a3 _$ x }1 i% e) k% ^: ]6 s1 s
1 u5 |' r) z3 t+ E+ N
( @- t" I2 g* e o' j
- ~, D0 C6 ^& |% K% q5 ~) p# C
阅读代码时,我们可以先关注最主要的方法,即CreatePurchaseOrder()和ReadPurchaseOrder()方法。如果并不希望了解过多构造PO对象的细节,通过阅读这样简短的方法,可以很容易地抓住这两个方法的实现,那就是通过构建一个PO对象,进行序列化,而在反序列化时,将获得的PO对象信息打印出来。) g, ~6 ]/ v; e
其实,糟糕的代码不一定就是初学者的“专利”,让我们看看NHibernate中的一段代码:public SessionFactoryImpl(Configuration cfg, IMapping mapping, Settings settings, EventListeners listeners) 4 |3 ^! L( f4 Z$ c& i{1 S4 e, W8 @& _2 z9 c, y
Init(); `* g% d. c7 c# d' {
log.Info("building session factory");' i* N% S# I( ~
2 E7 k1 T6 L2 _) L3 d' } properties = new Dictionary<string, string>(cfg.Properties); / f" ^% d" k1 `4 o interceptor = cfg.Interceptor; % O7 Z8 R0 I" v3 O this.settings = settings; o% m* U: m- F; c6 e5 y
sqlFunctionRegistry = new SQLFunctionRegistry(settings.Dialect, cfg.SqlFunctions); 1 a$ L% \9 y, f- f. J4 i eventListeners = listeners;/ L0 {, r; y4 g9 H( i0 b9 L
filters = new Dictionary<string, FilterDefinition>(cfg.FilterDefinitions); ; e, H ~1 g# z' W% D* t" X if (log.IsDebugEnabled) . P, v+ G9 e* R! y6 I5 V { 4 u1 ?. [8 {, D log.Debug("Session factory constructed with filter configurations : " + CollectionPrinter.ToString(filters));. s- U. g2 @ b
} # ? B0 x) M" }& i& i9 H( [; ]9 X8 ~& e. p5 g2 M* j
if (log.IsDebugEnabled) . G7 @/ X- G- D! i4 a {6 F6 b4 P( M8 z" I
log.Debug("instantiating session factory with properties: " + CollectionPrinter.ToString(properties)); G( K% F" G. U4 g ]( i& a7 x } 3 y0 w4 G% Z0 h- U& r8 R * e1 K6 s2 I, n' l1 U7 L try & N( B2 K- b' z. k- c1 a {; D9 W( l1 N6 G
if (settings.IsKeywordsImportEnabled)* u$ Z [* I/ l3 n5 W+ b( }
{- J. \4 t; y. i
SchemaMetadataUpdater.Update(this); / L, F# p- [2 ?: f! }) [ }+ [, d& f) O4 H4 L% e
if (settings.IsAutoQuoteEnabled)8 P) \: \3 t: j1 o8 C
{ & Q5 m# ^$ w" X; a: n; z SchemaMetadataUpdater.QuoteTableAndColumns(cfg);- [& V, D# X B5 ] D
}4 a _+ P3 }/ C/ \% J
}0 x5 n) L% N# v) l* l3 W i9 T" `1 l
catch (NotSupportedException); ^8 y0 e; _: u" B' }
{ : Z' A- m/ T. y0 s1 B7 C8 \# Q3 t& h // Ignore if the Dialect does not provide DataBaseSchema ! I% I6 ^' L- f' ]
} # I! K# p4 j/ O* o. C6 N- D& A2 C
#region Caches . J" Z2 v- I3 h B/ c& l1 N settings.CacheProvider.Start(properties);. {/ E+ [# E$ c: y
#endregion$ Q: K2 O3 j' L) l- J1 L7 H' l# @
! Q0 F' k' i- I3 e0 Q #region Generators* u' u6 [/ A3 s! }0 g+ _$ Z
identifierGenerators = new Dictionary<string, IIdentifierGenerator>(); & [! s& v) f3 E, F foreach (PersistentClass model in cfg.ClassMappings) ) e8 j: @* z3 B) q9 m { + P" p$ V7 ~! x; k* `8 V if (!model.IsInherited)/ f+ D. ?6 y7 n! y6 s
{0 }2 y$ z* P D9 ?, i4 h9 }$ ~1 ]
IIdentifierGenerator generator = : H5 y( V3 t7 C model.Identifier.CreateIdentifierGenerator(settings.Dialect, settings.DefaultCatalogName,% s" E$ w1 g: y3 n/ d
settings.DefaultSchemaName, (RootClass) model);; O. m4 ~: o% E. F
) y' t1 u' y! n- e identifierGenerators[model.EntityName] = generator; y U% m5 ^( ^
}. P' l8 x) M H5 q3 |1 z
}, q- ~5 y" C5 v
#endregion * ?: Y/ G. z& C$ z/ _$ T- H( ?) T- g' ~8 W, k. Y7 c% ?8 m! P
#region Persisters 6 P0 f- d7 R8 `5 S 4 b, k, K% |# c4 ?5 P3 ~ Dictionary<string, ICacheConcurrencyStrategy> caches = new Dictionary<string, ICacheConcurrencyStrategy>(); - s3 F U# ^$ W& E# M$ ] entityPersisters = new Dictionary<string, IEntityPersister>(); 1 h4 Q+ s1 X& Q5 o! s implementorToEntityName = new Dictionary<System.Type, string>(); , Y5 X* ?& z+ X+ o3 O, z- W8 K) r8 [- F- t
Dictionary<string, IClassMetadata> classMeta = new Dictionary<string, IClassMetadata>(); # C3 r5 S$ b, Z2 q1 _1 e9 [6 [- X$ X/ v* u* l2 g% v
foreach (PersistentClass model in cfg.ClassMappings)% `3 A1 j$ I7 o6 v
{( z$ I" U! H$ c8 @
model.PrepareTemporaryTables(mapping, settings.Dialect);% P5 |" Y) J4 A* r& l
string cacheRegion = model.RootClazz.CacheRegionName; # \) I0 ]3 i, }9 O, B: ~& Y1 ? ICacheConcurrencyStrategy cache; : o& B0 Z0 s# L# }, s if (!caches.TryGetValue(cacheRegion, out cache)) ( J+ q1 C: k S { 3 k) u Q) ]6 E# D) d, p cache = 8 c. j9 x, ]8 l CacheFactory.CreateCache(model.CacheConcurrencyStrategy, cacheRegion, model.IsMutable, settings, properties); 0 K- K& l/ n# r( L if (cache != null)- s$ h4 T: F) b: j& ~- O( x
{' M# o, @% V8 I8 h/ v
caches.Add(cacheRegion, cache);3 {8 G' ~1 `+ p/ @$ R+ L4 ^
allCacheRegions.Add(cache.RegionName, cache.Cache); 9 [) N& D k4 _% a4 H' g$ W }, e- @) N0 K# _
}# B8 V* S3 P9 P8 W
IEntityPersister cp = PersisterFactory.CreateClassPersister(model, cache, this, mapping); ( Q. a' d. a' P9 u5 e entityPersisters[model.EntityName] = cp; 4 g5 `6 f' t# D w9 k6 T classMeta[model.EntityName] = cp.ClassMetadata;1 b' M6 m+ D- o- P! Y) [
3 b- p6 q4 r0 j
if (model.HasPocoRepresentation)9 v3 A6 |0 [+ k
{2 Q- m! d- T4 ^# `0 e4 p
implementorToEntityName[model.MappedClass] = model.EntityName; ( k0 h7 _9 ~* y8 { }3 m/ C# z2 B/ B: i( M' k7 ~
}% ^' N7 @) D6 T- B) u7 {1 C6 D
classMetadata = new UnmodifiableDictionary<string, IClassMetadata>(classMeta);! u5 A# a' C6 p+ M5 U3 P% c5 n- M
6 P) B0 ^& x# s' T Dictionary<string, ISet<string>> tmpEntityToCollectionRoleMap = new Dictionary<string, ISet<string>>();: H5 I. {) n' E7 l5 y
collectionPersisters = new Dictionary<string, ICollectionPersister>();! v: {2 x8 [; f
foreach (Mapping.Collection model in cfg.CollectionMappings)8 V; m! T' B( m! t
{ ( Z" q1 D; d, ?; | ICacheConcurrencyStrategy cache = , g) j. m7 p& {1 ?3 `1 [ CacheFactory.CreateCache(model.CacheConcurrencyStrategy, model.CacheRegionName, model.Owner.IsMutable, settings, - k4 N$ _1 t. m2 s properties); $ _+ p) U& U, H { u8 J if (cache != null)9 Q d- @# X) q. t" o4 S
{& |* s, `* ^; R/ Y0 Z) Y
allCacheRegions[cache.RegionName] = cache.Cache;* |* ?( G( R/ V
}! F% P9 c+ Q. Y5 ~# |
ICollectionPersister persister = PersisterFactory.CreateCollectionPersister(cfg, model, cache, this);5 }' X. h" M2 y6 G) r; r7 T' \
collectionPersisters[model.Role] = persister;. V( u0 |8 e% S0 X
IType indexType = persister.IndexType; # f' r% ~! E5 H o/ j* ]) q if (indexType != null && indexType.IsAssociationType && !indexType.IsAnyType) # ^. c" m5 a7 Z- ]( O) _ s {8 Q4 u/ n1 S% J# d- Z; ]9 V u5 C; k, P
string entityName = ((IAssociationType) indexType).GetAssociatedEntityName(this);2 o. a# ]# K* I$ Q6 G$ C3 [
ISet<string> roles;" L! Y3 g$ K* k ~
if (!tmpEntityToCollectionRoleMap.TryGetValue(entityName, out roles)) : D" Q) ]' I" I) n& h1 P {' S% _9 O; ?. |* K
roles = new HashedSet<string>(); + k$ ~6 `: m3 t+ W! u* ?0 G tmpEntityToCollectionRoleMap[entityName] = roles;. c- L# C$ l1 j8 [* O8 y; _
}/ f ^* Q: H4 J' b# }) S7 `+ |6 ]
roles.Add(persister.Role); 6 s# Z0 E3 @! r& d" i }: e4 E! \6 F9 V: n0 @
IType elementType = persister.ElementType; , `6 L+ |& K- d' c" I if (elementType.IsAssociationType && !elementType.IsAnyType) / t& q" p: Y4 _+ n+ A; t4 f: _* d {2 d# _& y4 H. K8 \
string entityName = ((IAssociationType) elementType).GetAssociatedEntityName(this);# L: Q- V# t3 v* V
ISet<string> roles;& ~6 K% r& R8 A. |+ ^2 d' E$ K
if (!tmpEntityToCollectionRoleMap.TryGetValue(entityName, out roles))1 h/ v0 T, m7 R4 r
{. G& Y/ o$ u1 u
roles = new HashedSet<string>();; f$ L- c! u9 {, g( A" a$ l9 _! V
tmpEntityToCollectionRoleMap[entityName] = roles; 8 V$ d! |! C+ R4 S$ x+ t0 F }% I. Y: B1 p& B+ w+ ], Q5 M0 e
roles.Add(persister.Role);) O- [' s% G1 M0 X9 t) n
} # A, f/ Y8 M; w: I" J3 t5 k }* \# r* ]0 B7 x3 z
Dictionary<string, ICollectionMetadata> tmpcollectionMetadata = new Dictionary<string, ICollectionMetadata>(collectionPersisters.Count); 5 {( Q6 Y/ }; ~+ I h, r4 T; c foreach (KeyValuePair<string, ICollectionPersister> collectionPersister in collectionPersisters)+ ]% C* r: L4 J8 V# Y0 d
{+ K% l$ o# \! t1 q: _4 z3 q- F
tmpcollectionMetadata.Add(collectionPersister.Key, collectionPersister.Value.CollectionMetadata); 0 o' W& X9 {% n8 _/ i+ m# C }: G* Q- E2 Q5 U) J% i
collectionMetadata = new UnmodifiableDictionary<string, ICollectionMetadata>(tmpcollectionMetadata); $ ~7 }! `1 w' O: L) ` collectionRolesByEntityParticipant = new UnmodifiableDictionary<string, ISet<string>>(tmpEntityToCollectionRoleMap);0 ^" a+ x) c# N( q4 K
#endregion $ u* n6 X, Y9 s/ ^/ O9 f( D8 z 5 u* Y0 W4 i" x! z2 F #region Named Queries 5 ], \# |0 y; L6 d0 ~8 E. i namedQueries = new Dictionary<string, NamedQueryDefinition>(cfg.NamedQueries); & j# Q2 ]' {, Z6 t$ g namedSqlQueries = new Dictionary<string, NamedSQLQueryDefinition>(cfg.NamedSQLQueries);0 j5 G' e5 Y o/ S9 p
sqlResultSetMappings = new Dictionary<string, ResultSetMappingDefinition>(cfg.SqlResultSetMappings);; b) G6 e* a- e7 U0 d8 r
#endregion$ h% ~' A2 C: X2 V0 o" v
5 u: c# S" Q; q8 J: v2 ^ imports = new Dictionary<string, string>(cfg.Imports); 7 L# C" G0 P6 X3 k9 P! i- U: \; l3 {2 L9 V! p
#region after *all* persisters and named queries are registered R2 x: k* X# O9 F- F foreach (IEntityPersister persister in entityPersisters.Values) 7 v3 C4 y1 B8 C6 I6 ?( S# E+ ? { 2 S. ]( c6 w# w9 h8 S( Q8 @4 H persister.PostInstantiate(); e! u6 Z& @) G1 i) C& A
} 9 ?& l1 c' o# s foreach (ICollectionPersister persister in collectionPersisters.Values)* f6 j$ |9 A4 |4 O; G, U
{) Z0 w8 @9 i% f8 A0 k; K/ S( ]
persister.PostInstantiate(); $ D. a+ T# A; I; D } - I. s3 Q/ ~5 S4 L4 Q y0 L #endregion" Z+ m$ P$ O- U p$ v: M
: \: Z! n$ W, g8 N/ F$ ~1 c# F
#region Serialization info 6 e: z' W9 T$ e# P9 r: c4 s! X0 D" A
name = settings.SessionFactoryName; # e2 n. c0 R! P" v; u. R. P$ q5 r try' V& y4 u2 M/ w8 i" e$ W
{ ^" [: O% ?: L" D* S( W uuid = (string) UuidGenerator.Generate(null, null); - y% a. ]# Q# C" i. | }% |' Y5 `4 T b% c! c
catch (Exception)% Q& u, c, `1 L H! @, c b/ ?
{2 o% u6 p# X5 t9 q/ v& h8 P
throw new AssertionFailure("Could not generate UUID");# X& b( `2 l6 M. g6 D
}' _3 e# i& r i( W4 \. W; w l0 z
" T6 S" D+ N, _" P2 r( R9 r& i: l0 t SessionFactoryObjectFactory.AddInstance(uuid, name, this, properties);7 H4 X I9 j) O6 f; k
# X& {" o0 p' Y# t3 _
#endregion ' g% I% V0 f; Y3 v/ z7 [$ b, f& u k4 l; ^
log.Debug("Instantiated session factory"); , L5 R( C; z& H% Q( K4 b" \ 6 q) b$ |$ \% N, H3 H* p- O #region Schema management& r$ ~3 d' A# K7 g$ V9 N
if (settings.IsAutoCreateSchema) ! }% e. {: D+ Y3 Y { / i' G! @: S! F* P new SchemaExport(cfg).Create(false, true); 1 `1 Y a7 {& q9 V1 D8 C }1 r% I: r: S8 p
8 c. _. x9 Z- W. j
if ( settings.IsAutoUpdateSchema )7 G$ U4 B- @5 }# m: o
{ Q& ~: `2 m/ T' ?! D& C
new SchemaUpdate(cfg).Execute(false, true); 5 e+ r: `+ ^& v2 ?" U* N5 J ?. o0 Q } : p9 W _, H, W8 z' O if (settings.IsAutoValidateSchema)# p; } i6 Z9 a, P
{ 6 n4 l: ]- D4 _1 g8 { new SchemaValidator(cfg, settings).Validate(); & F4 g+ ^2 D' G( Z7 M }) L* S& @: {6 n8 l3 C
if (settings.IsAutoDropSchema) 4 {7 z1 q W- k q6 J: x9 \ { ( _: b0 S& l# I+ C2 @' G schemaExport = new SchemaExport(cfg); , H: f, k! @. q! T) l+ o% M3 a7 M } 0 P1 k1 k9 d" a( A* n: W: \+ N #endregion& ~ [% K8 R5 q G" U
' X" B( E7 O4 k' Z. N
#region Obtaining TransactionManager7 @- ^ r4 V; N9 w- [! r
// not ported yet 7 Y! e3 p0 y& b" J0 r; q0 v' J- { 1 T3 A% i2 @ g( _! J# R& o9 E3 e9 ]
#endregion , [2 s# o/ R t2 y& Q& S0 ` 7 ]( Q8 d7 \' D4 e9 N currentSessionContext = BuildCurrentSessionContext();' ~ ?# i2 H( G8 l
' b% D l; t5 _; p; v% z5 \- z+ K if (settings.IsQueryCacheEnabled)) @' N! f1 T% h& d# ^
{ - n7 Z4 r! O8 C/ n updateTimestampsCache = new UpdateTimestampsCache(settings, properties);2 p; E( w+ p& z) ^5 d8 i1 m) q
queryCache = settings.QueryCacheFactory.GetQueryCache(null, updateTimestampsCache, settings, properties); 4 n/ O- j: @* Z( q queryCaches = new ThreadSafeDictionary<string, IQueryCache>(new Dictionary<string, IQueryCache>()); 9 R: _. W- U# S; | } 6 U) `6 ~ ^9 K2 n- K else 2 d! q, a5 T- X3 l6 F {3 I2 l% {+ ^5 l! s8 s+ i& y7 j, f
updateTimestampsCache = null; 3 q7 v9 m m; B9 {0 V' Y( H/ h queryCache = null;! j. l1 i- h( B5 l9 u& Z; U
queryCaches = null;. c, T$ S& C$ M! W! ^9 c1 R
}0 w5 n8 F' w; `7 j( D
; W* m+ W4 b: p #region Checking for named queries * t/ o( z! n" @% W7 }2 a, o# o8 Q if (settings.IsNamedQueryStartupCheckingEnabled)- y. A$ _' s) F
{ c+ e5 x9 f/ o1 V7 C" k- Y
IDictionary<string, HibernateException> errors = CheckNamedQueries();1 F- H1 a' F) y
if (errors.Count > 0) 2 E6 T* N4 E/ [ z" s { 1 u: G2 s+ L; n7 @- G StringBuilder failingQueries = new StringBuilder("Errors in named queries: ");5 |5 { v- Q3 \3 K; F6 B
foreach (KeyValuePair<string, HibernateException> pair in errors) ! N; ]! C- F& A. y { # J& E2 t2 t$ r" V! ?" w+ { failingQueries.Append('{').Append(pair.Key).Append('}');; }+ Z' |0 ?' @7 X
log.Error("Error in named query: " + pair.Key, pair.Value); : v0 `6 r1 X" ]+ U& w) }1 P }9 Q8 F: t- ^. m3 c. Q
throw new HibernateException(failingQueries.ToString()); 4 S- }. T8 a0 ^4 I, M: ~ \ } " X, d( r& w( h+ A( B+ b# B7 S } , o! F6 u7 A8 d7 V% b' H' r1 M8 { #endregion& ?3 K! m, Z P7 H1 Y* }
5 b1 Y: K& H. a) N X8 R' O) x Statistics.IsStatisticsEnabled = settings.IsStatisticsEnabled; 2 C7 s- z, `6 k: i! U$ m# T4 B% w) ?2 m
// EntityNotFoundDelegate7 Y V6 Z' l: H: L, S( T
IEntityNotFoundDelegate enfd = cfg.EntityNotFoundDelegate; $ {# C8 B, s, s if (enfd == null) 5 p+ J4 P9 o* n# S* z8 r" ^ {! P( L2 B3 q# A; d& ?; r" s4 E
enfd = new DefaultEntityNotFoundDelegate(); / J% O% Y! i" y" ]& S, O } 7 s1 o- K5 Y ]3 Q: z; t9 t entityNotFoundDelegate = enfd;2 r R% v! ^$ W: t" T0 }
}0 D! n" S p0 @$ I$ @
; m9 H8 U( |* l0 G; d0 [% l8 m& k7 Q( X. H+ r
% ?2 f! z0 }5 j5 _5 Z3 X2 K5 c! y; h( c
这是类SessionFactoryImpl(它实现了ISessionFactoryImplementor接口)的构造函数,其目的时是通过Configuration以及Setting中的某些值,去初始化SessionFactoryImpl,然后构建该类的对象。坦白说,我从来没有看过如此“浩瀚无垠”的构造函数。幸好,Visual Studio提高了Region,否则,更让人头疼。(我在想,既然代码的编写者已经利用了Region来分割实现,为何不进一步将其分割为小的方法呢?)% q9 |) l$ z$ l, J l' x
看这样的代码,我们能够轻易读懂吗?5 k8 q+ [8 c* c. E2 Z" N
拙劣代码可谓遗患无穷。在《程序员修炼之道》一书中,提到了所谓“破窗效应”,即“没修复的破窗,导致更多的窗户被打破”。丑陋的代码如果只有一个小的片段,看似无关紧要,就像一幢大楼的一扇破窗一般容易让人忘记。随着时间的推移,当这些丑陋代码不知不觉蔓延到整个项目中时,我们才发现这一幢大楼已经满目疮痍了。“一屋不扫,何以扫天下”,程序员应该从小处着手,未来才可能写出优雅的代码。