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