<?xml version="1.0" encoding="utf-8"?>
<rss version="2.0">
  <channel>
    <title>架构设计 - 老赵点滴 - 追求编程之美</title>
    <link>http://blog.zhaojie.me/architecture/</link>
    <description>先做人，再做技术人员，最后做程序员。打造国内最好的.NET技术博客。</description>
    <language>zh-cn</language>
    <managingEditor>jeffz@live.com (老赵)</managingEditor>
    <webMaster>jeffz@live.com (老赵)</webMaster>
    <pubDate>Wed, 26 Aug 2009 08:54:00 GMT</pubDate>
    <lastBuildDate>Wed, 26 Aug 2009 08:54:00 GMT</lastBuildDate>
    <ttl>60</ttl>
    <item>
      <author>jeffz@live.com (老赵)</author>
      <category domain="http://blog.zhaojie.me/language/">语言编程</category>
      <category domain="http://blog.zhaojie.me/discussion/">思考讨论</category>
      <category domain="http://blog.zhaojie.me/architecture/">架构设计</category>
      <title>2010年5月《程序员》杂志“架构师接龙”栏目中的问答</title>
      <link>http://blog.zhaojie.me/2010/05/programmer-magazine-2010-5-architect.html</link>
      <guid>http://blog.zhaojie.me/2010/05/programmer-magazine-2010-5-architect.html</guid>
      <description>&lt;p&gt;上个月《程序员》杂志向我约稿，希望我可以参加5月份的“架构师接龙”栏目，我略为犹豫了一下便答应了。“架构师接龙”是一个问答形式的栏目，每期由一个人提问，并由另一个人回答。回答的一方便是下期的提问者。这次提问的架构师是&lt;a href="http://t.sina.com.cn/"&gt;新浪微博&lt;/a&gt;的技术经理&lt;a href="http://timyang.net/"&gt;杨卫华&lt;/a&gt;。他提出的问题包括语言选择与架构设计、NoSQL存储方案的取舍、微博类系统的架构等多个方面。杨卫华是国内技术社区一等一的高手，这使得我在回答问题时更有小心翼翼地班门弄斧之感。如果您对某些问题感兴趣，也不妨来一起讨论一下。&lt;/p&gt;

&lt;h1&gt;语言选择与架构设计&lt;/h1&gt;

&lt;p&gt;&lt;strong&gt;提问：&lt;/strong&gt;很多架构师表示编程语言不重要，架构设计思想才重要，但是大部分团队都是非常依赖某种语言的，甚至很多项目负责人也存在对某种语言存在偏好而对另外一种语言反感的现象。你怎么看待编程语言选型问题？同时业界也存在另外一种现象，很多前沿技术研究者对一些新兴语言如Erlang, Go等表示出狂热，你对团队或项目中是否引入这些新的语言持什么观点？&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;回答：&lt;/strong&gt;在我看来，编程语言的选择也是至关重要的。诚然，架构的设计思想可谓直接决定了系统本身的质量。与此相反，从理论上说，只要是图灵完备的语言，就不存在“能力”上的根本差异，任何工作都是可以实现的。但是，有一点也无法被忽视，那便是我们使用的语言，往往也会影响，甚至决定了我们的思维方式。 &lt;/p&gt;

&lt;p&gt;举个有些极端的例子，如果人们还在使用汇编语言进行开发，那么估计程序员的思维永远无法跳出“子过程”这个抽象级别，什么面向对象设计，函数式编程几乎无从谈起。人们在生产和学习过程种会引发一些需求，因而需要产生一些工具来辅助学习和生产，而“语言”便是此类工具之一。只有利用高级语言，人们才能有效地把真实世界抽象成计算机这些机器盒子能认识的东西。 &lt;/p&gt;

&lt;p&gt;如今，可用于构建项目可选的主流语言往往都会有多种，有时候的确会发现，好像不同的语言──例如Ruby和Python──从各方面来说并没有太大区别。这其实也是比较正常的，因为某些（甚至是大部分的）语言特性，并没有对我们的“思维”产生影响。 &lt;/p&gt;

&lt;p&gt;举例来说，某些喜欢Ruby的朋友认为Ruby语言的编程体验非常良好，好比它的数组操作可以直接做加法或减法： &lt;/p&gt;

&lt;pre class="code"&gt;array = ['aaa', 'bbb', 'ccc'] - ['ccc', 'ddd'] &lt;/pre&gt;

&lt;p&gt;或者说，在Python里交换两个变量的值也只需要一行代码（大部分语言中可能需要借助中间变量）： &lt;/p&gt;

&lt;pre class="code"&gt;a, b = b, a &lt;/pre&gt;

&lt;p&gt;但是，就我个人观点来说，这些语言特性，虽然它们的确可以让编程工作变的相对轻松一些，例如可以让我们少些一点代码，但终究没有改变，或是表现出另一种编程思维。这样的语法特性，一般来说都可以通过构建一些简单的辅助函数来做到类似程度的“简化开发”（如上面Ruby的例子），对于那些非“即写即抛”的程序来说，这些特性的优势并不明显。 &lt;/p&gt;

&lt;p&gt;而与此相比，Ruby的Mixin机制和Python的Decorator就不仅仅是“语法糖”，而是比较重要的语言特性了，因为它们可以带来或大大简化某些十分有用的编程模式。 &lt;/p&gt;

&lt;p&gt;不过对于语言的选择有时候还需要往更高处看。现今一些新出现，或者新流行起来语言，对于系统开发方面的影响则更为深远。举个例子，目前说起并发/并行程序设计，无法忽略的便是Erlang语言。这门语言提供了一种构建轻量级计算单元（在Erlang中被称为“进程”），使用发送消息（Message Passing）的方式进行相互间的通信。这种做法避免了共享状态（Shared State）方式下容易出现的各种问题，并且在其独特的虚拟机实现下可以得到很强大的并发能力。但是，Erlang的任务调度机制有个特点，那便是它会为每个“进程”分配相同的计算能力。这样，如果系统中有1000个进程，那么每个进程得到的计算能力便会是100个进程时的十分之一。这种调度方式对于某些类型的应用来说可能并不合适，因为它可能在并发压力增大的情况下，造成吞吐量的降低甚至完全停止服务（因为每个任务都超时了）。Erlang的这个特性往往会直接影响到系统的架构方式。 &lt;/p&gt;

&lt;p&gt;不过在某些场景下，我们也可以选用其他的语言。例如Scala，它同样提供了基于Actor模型的消息传递并发机制。但是它的调度方式与Erlang不同（事实上由于平台功能限制，它也无法实现Erlang的调度方式）。由于Scala的Actor模型构建与JVM之上，因此它只能准备一个线程池，让其中的线程不断地处理消息的传递及处理任务，而额外的任务则会在队列中等待。因此，Scala使用的并不是Erlang那样完全公平的调度方式，但是这样反而可以优先处理先出现的任务，保证稳定的吞吐量。 &lt;/p&gt;

&lt;p&gt;因此，Erlang和Scala这两种不同的调度机制，决定了它们适合不同的应用场景，或是系统架构的不同方式。我相信Facebook选用Erlang构建聊天平台，Twitter选用Scala构建消息中间件都是有这方面考虑的。 &lt;/p&gt;

&lt;p&gt;当然，调度方式更像是由平台决定，而不是语言决定的。不过在刚才的特定问题上我认为两者其实是统一的。因为Erlang既是门语言，也代表了一个平台。而Scala虽然是JVM平台上众多语言之一，但也只有它能够优雅的实现Actor模型的消息传送机制。我始终认为，一个语言特性只有真正“好用”，它才能被人们广为接受。例如，使用Java语言能实现Actor模型吗？能，但是它缺少Scala那样灵活的函数式语法，以及模式匹配等特性，因此无法构建出一个好用、易用的Actor框架，自然也就无人问津了。这其实也是“语言影响思维方式”的典型案例之一。 &lt;/p&gt;

&lt;p&gt;异步及并行是如今系统构建不可或缺的因素。如今的新语言大都在这方面下了很大功夫。除了Scala和Erlang以外，在微软.NET平台上的新语言F#引入的创新特性“计算表达式（Computation Expression）”，使用类似于Monad的机制大幅度简化了异步程序的开发难度。而JVM中的Clojure语言也引入了软件事务内存（STM，Software Transactional Memory）。我们几乎可以这么说，如今每种新出现的语言都有独特的“杀手级”特性，它们都是影响系统开发的重要因素，使用语言本身的支持可以显著降低系统的开发难度，增加可维护性和健壮性，这些并非是由架构改进可以轻松获得的效果。 &lt;/p&gt;

&lt;p&gt;如今“多语言”开发逐渐成为一种趋势，例如在Facebook的各个子系统分别使用了C、C++，Erlang，Java等多种语言/平台，然后使用PHP作为黏合剂连接起来。而Twitter也毫不例外地使用了Ruby，C，Scala和Java。现在的系统日趋复杂，几乎没有任意一种工具可以完全适合系统的全部开发，为系统不同的组成部分选择合适的语言，也是如今架构师需要面临的挑战。 &lt;/p&gt;

&lt;p&gt;与过去不同，现在即使确定构建系统所用的平台之后──如JVM，也会发现语言的选择余地会有很多，不同的语言的确也有不同的特性，可以带来一些特别的优势。例如，利用Ruby的动态特性，便可以方便地进行单元测试。而系统的生产部分代码，可能便可以选用Scala等静态编译型语言，以便借助更完整的静态检查工具来确保更加稳固的产品质量。 &lt;/p&gt;

&lt;p&gt;对于新语言的选取，不同风格的架构师会采取不同的策略。例如保守的架构师可能会根据语言所在社区是否活跃，语言相关资源是否丰富，相应的程序员是否容易招聘来考虑语言或平台的选择。这种做法是十分正常的。但是万事都讲究个平衡，在某些情况下“保守”和“抱残守缺”或“固步自封”之间仅一步之遥。 &lt;/p&gt;

&lt;p&gt;我个人的风格相对比较“激进”，十分乐于吸收和尝试一些新事物。我的建议是，每个技术团队都应该挑选出几个技术水平较高，经验较为丰富的成员，广泛的吸收新事物的发展，并在合适的时候向团队及生产环境做出提案，以改进系统的开发效率或者质量。由这些高级技术人员进行引导，往往可以较好的预估新技术对于产品的影响，即便出现了一些问题也可以设法自行解决。 &lt;/p&gt;

&lt;p&gt;据我了解，一些较为活跃的技术团队，尤其是一些Web 2.0产品的技术团队，在这方面都有比较好的实践。&lt;/p&gt;

&lt;h1&gt;NoSQL存储方案的选型&lt;/h1&gt;

&lt;p&gt;&lt;strong&gt;提问：&lt;/strong&gt;最近很多公司有向NoSQL方向发展趋势，很多架构师也关心是否需要将关系数据库转向NoSQL，请问能给正在选型的架构师哪些建议？ &lt;/p&gt;

&lt;p&gt;&lt;strong&gt;回答：&lt;/strong&gt;我的个人看法是，NoSQL本身是好东西，但是在使用的氛围方面稍有一些扭曲。可能是受到关系型存储方式的“压抑”太久，如今冒出一个NoSQL运动让人眼前一亮，不由得热血沸腾起来。 &lt;/p&gt;

&lt;p&gt;NoSQL的出现，原本不是为了完全取代关系型数据库，而是为了应对关系型数据库在性能和伸缩性方面的缺陷而提出的存储方式。NoSQL不应该是“No SQL”，更为妥当的方式应该是“Not Only SQL”。 &lt;/p&gt;

&lt;p&gt;放眼如今比较成功的NoSQL应用，似乎除了Google由于数据规模，资源沉淀等原因之外，其他系统大都是将NoSQL作为一种优化的手段在使用，而并非是作为系统的主要存储方式，它们主要使用的存储方式依然是MySQL等关系型数据库。而事实上，各系统也是在架构演变过程中，发现关系型数据库成为了系统优化的瓶颈，进而在一定程度上引入NoSQL存储方式以改善性能。 &lt;/p&gt;

&lt;p&gt;例如就在不久之前，SourceForge宣布将在系统中引入MongoDB，而Twitter也打算开始使用由Facebook创建的Cassandra。但是以SourceForge与Twitter目前基于关系型数据库所支撑起来的规模，也已经是无数系统难以企及的目标了。更何况，如Stack Overflow这样号称全世界最大的程序员网站，作为存储后端也只是使用了单台关系型数据库。 &lt;/p&gt;

&lt;p&gt;毕竟，关系型数据库的性能并非差到不可接受，NoSQL的优势也只有在达到一定规模时才能体现出来。而且，除了存储方式以外，系统中可以优化的地方还有很多。例如最传统的，缓存，一个实现较好的缓存机制可以减少95%以上的数据库访问，这对于系统性能的影响也是相当巨大的。 &lt;/p&gt;

&lt;p&gt;在目前，使用NoSQL存储方式的另一个不便之处便是工具的缺失。我在项目中也使用了MongoDB，一个十分明显的体会便是，对于MongoDB的操作比关系型数据库要麻烦不少。例如在访问关系型数据库时可以利用现成的映射工具，经过多年发展此类工具也已经变得非常灵活、高效，能够应对绝大部分的使用场景。而在使用MongoDB时，我似乎又有了回到当年裸写JDBC的感觉了，甚至对于某些平台来说，连一个成熟的驱动（例如有连接池的支持）都需要亲自动手开发一个。对于一个有经验的开发人员来说，便写一些“够用”的代码自然不是难事，不过这的确也是一件会影响投入产出比的事情。 &lt;/p&gt;

&lt;p&gt;此外，NoSQL虽然性能高，但这也是通过在一定程度上牺牲数据的完整性或一致性的保证换来的，传统的关系型数据库却在这方面投入了很大的精力，例如事务机制，虽然会降低性能，但是却保证了数据的一致性。但是，如今的NoSQL存储几乎都没有提供类似的机制（毕竟有个无法回避的CAP规律），这样多个相关的操作一旦中断（例如出现了异常），则很容易造成数据“此长彼短”的现象。而且，如今的NoSQL产品出于性能考虑，几乎无一例外会带有一定程度的缓存机制，不会将新建或更新的数据直接写入磁盘。因此，如果没有一个集群环境，在遇到突发状况时则很可能带来数据丢失的情况。对此，如MongoDB明确指出，它对于单机的持久性并不十分重视，它的设计人员以此换来更为重要的参数：性能。这意味着，一旦使用NoSQL作为主要存储方式，则往往会需要同步跟进一些周边措施，例如可能要在保证数据的最终一致性方面投入较多的精力。 &lt;/p&gt;

&lt;p&gt;当我们确定要选择NoSQL存储方式的时候，则必须根据自己的业务特征选取特别的NoSQL产品。目前NoSQL主要分为四大类：BigTable，Key-Value，文档型，及图数据库。它们有各自的性能优势及适用范围。例如Key-Value存储方式支持的查询方式非常有限，但是由于结构简单，它的性能和伸缩性可谓傲视群雄。而文档型数据库，如MongoDB，它所支持的查询方式非常灵活，并内置Map Reduce机制，可以直接输入JavaScript脚本进行一些特殊的数据处理及汇总。而如Neo4j这样的图数据库，由于直接支持“节点”、“（有向）关系”等概念，则对于一些关系型数据库、文档型数据库难以应对或建模的查询或遍历方式（如最短路径计算），就有了非常直接、自然且高效的支持。 &lt;/p&gt;

&lt;p&gt;总之，架构师选择的不是SQL或NoSQL本身，而应该只是“最合适”的东西。&lt;/p&gt;

&lt;h1&gt;关注的方向和领域选择&lt;/h1&gt;

&lt;p&gt;&lt;strong&gt;提问：&lt;/strong&gt;很多架构师都喜欢学习Google, Facebook等大型系统的经验，但另外不少架构师则认为绝大多数网站都不会成长成一个“大型网站”。绝大多数工程师都没有能力建立和维护一个类似GFS的系统。对绝大多数网站而言，把时间耗在所谓“大型网站”的架构上没有意义，你怎样看待这种说法，架构师应该如何选择关注的方向和领域？&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;回答：&lt;/strong&gt;我在这方面的看法是，虽然Google，Facebook等大型系统的规模对于绝大多数人来说可能是永远无法接触到的，但是它们的经验及措施可能会给我们带来一些其他方面的体会。 &lt;/p&gt;

&lt;p&gt;例如，Map Reduce原本是函数式编程中再普通不过的概念和手段，但是Google将它和GFS等其他基础设施一结合，便成为了一个无比强大的分布式计算技术。但是，Map Reduce它本身还是十分简单的东西（Google Map Reduce实现的复杂点主要还是在于GFS），它也并非Google专有的东西，我们受到这样的启发也可以将其用到别处。例如在MongoDB和CouchDB中都内置了MapReduce支持，在去年的QCon Beijing大会中，FreeWheel公司在它们的广告平台中，也使用了自己的方式实现了Map Reduce计算机制。 &lt;/p&gt;

&lt;p&gt;因此，即便是无法成为真正的巨人，也可以关注巨人成长过程中所吸取的经验教训，从中也可以得到一些启发。即使是当作一个有趣的故事去了解也好，即使是为了打开眼界也好。有时，我们需要的可能只是一个不经意的提示。&lt;/p&gt;

&lt;h1&gt;微博类产品的架构难点&lt;/h1&gt;

&lt;p&gt;&lt;strong&gt;提问：&lt;/strong&gt;目前国内很多互联网门户都在做微博产品，你觉得微博技术架构的主要难点在哪些方面？&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;回答：&lt;/strong&gt;从复杂度上面来说，微博产品的业务是相对比较简单的，我认为它在技术架构上有两大要素：消息传递与缓存。 &lt;/p&gt;

&lt;p&gt;微博产品从产品性质上来说几乎完全就是一个消息分发平台，因此一个良好的消息传递机制是至关重要的。当一个用户发出消息之后，它可以被许多人观察到。对于一个名人来说，被数十万用户所追随是一件非常普通的事情。那么此时，如果期望所有的追随者都即时地看到消息这几乎是件不可能的事情，因此在实现时我们往往需要构建一个消息队列，将消息快速地派送至队列中等待处理，最终将这条消息“陆续地”显示在每个追随者的时间轴上。这里势必会产生延迟，但对于业务质量来说并非不可接受。但显然这个延迟也不可以太长，在Twitter上这个平均延迟是500毫秒，从绝对数值上看并不算太短，但也已够用。Twitter在这方面的处理方式是利用Scala的Actor模型及Apache Mina编写了一个分布式的消息传输框架Kestrel，它具有快速、轻量（包括注释才不到2000行代码）、持久、稳定等特性，但不具备事务性，也不保证消息的顺序处理。因此可以这么说，Kestrel是一个Twitter根据自身需求“定制”出的消息传输机制。 &lt;/p&gt;

&lt;p&gt;另外一个要点便是每个大型系统都不可或缺的缓存机制。有人说缓存就好比万能膏药，哪儿不舒服就再哪儿擦点便能见效。这话有一定道理。如Twitter便设计了相对复杂的多级缓存机制，几乎对于每个IO密集的地方都进行了缓存。例如记录ID序列的向量缓存（Vector Cache），纪录每条消息等具体内容的行缓存（Row Cache）。此外，由于它的API访问量很大，仅仅是从消息内容转化成API的输出形式（可能只是一些字符串连接操作）也会消耗较多代价，因此Twitter还为消息的API输出形式设计了片断缓存（Fragment Cache）。最后自然还有对某些热门页面的页面缓存（Page Cache）。除了页面缓存之外，其他缓存的命中率都在95%以上，可见缓存机制对于Twitter系统的重要程度。值得注意的是，向量缓存及行缓存都是Write Through的，这意味着基本上所有的新数据都在缓存中存在一份拷贝。正如Twitter的Evan Weaver在QCon London 2009会议上讲的那样：Everything runs from memory in Web 2.0。 &lt;/p&gt;

&lt;p&gt;最后，对于微博类应用来说，可能会因为某个突发事件造成访问量暴增的现象，如何抵抗住消息轰炸也是一个重要的课题。例如Twitter使用了云计算的方式来应对此类问题，在需要的时候它便会租用更多的计算资源。不过增加服务器只是纯硬件的投入，而架构设计能否顺利且充分地利用起新增的设备也是一个值得关注的地方。从这个角度看，一个高效的分布式消息传递机制在这里会扮演重要的角色。如果有了合适的消息机制，首先能够将消息负载较为容易地平衡至多台服务器上，其次即使是在压力增大的情况下，响应时间虽然会按线性增长，但是系统的吞吐量还是可以保持在正常的水平。 &lt;/p&gt;

&lt;h1&gt;给未来架构师的建议&lt;/h1&gt;

&lt;p&gt;&lt;strong&gt;提问：&lt;/strong&gt;很多工作2-3年的软件工程师谈到职业规划都是希望往架构师方向发展，请问能给这些正在成长的工程师哪些建议？如何才能成为一个优秀的架构师？ &lt;/p&gt;

&lt;p&gt;&lt;strong&gt;回答：&lt;/strong&gt;其实我也不知如何给出一些有效而具体的建议。我认为架构师不是一个职位或是职责，而更像是一种思维方式。其实只要打开眼界，不断吸收和关注技术及业务的发展，待积累到一个合适的时候便可以对系统架构提出自己的思路及建议的时候，那你就是一个架构师了。 &lt;/p&gt;

&lt;p&gt;其实每个程序员都可以是架构师。&lt;/p&gt;</description>
      <comments>http://blog.zhaojie.me/2010/05/programmer-magazine-2010-5-architect.html#comments</comments>
      <pubDate>Fri, 21 May 2010 01:48:19 GMT</pubDate>
      <lastBuildDate>Fri, 21 May 2010 01:48:19 GMT</lastBuildDate>
    </item>
    <item>
      <author>jeffz@live.com (老赵)</author>
      <category domain="http://blog.zhaojie.me/architecture/">架构设计</category>
      <title>一种以ID特征为依据的数据分片（Sharding）策略</title>
      <link>http://blog.zhaojie.me/2010/03/sharding-by-id-characteristic.html</link>
      <guid>http://blog.zhaojie.me/2010/03/sharding-by-id-characteristic.html</guid>
      <description>&lt;p&gt;假如您有一个应用程序，随着业务越来越有起色，系统所牵涉到的数据量也就越来越大，此时您要涉及到对系统进行伸缩（Scale）的问题了。一种典型的扩展方法叫做“向上伸缩（Scale Up）”，它的意思是通过使用更好的硬件来提高系统的性能参数。而另一种方法则叫做“向外伸缩（Scale Out）”，它是指通过增加额外的硬件（如服务器）来达到相同的效果。从“硬件成本”还是“系统极限”的角度来说，“向外伸缩”一般都会优于“向上伸缩”，因此大部分上规模的系统都会在一定程度上考虑“向外”的方式。由于许多系统的瓶颈都处在数据存储上，因此一种叫做“&lt;a href="http://en.wikipedia.org/wiki/Shard_(database_architecture)"&gt;数据分片（Database Sharding）&lt;/a&gt;”的数据架构方式应运而生，本文便会讨论这种数据架构方式的一种比较典型的实现方式。&lt;/p&gt;  &lt;h1&gt;简介&lt;/h1&gt;  &lt;p&gt;数据分片，自然便是将整体数据分摊在多个存储设备（下文统称为“数据分区”或“分区”）上，这样每个存储设备的数据量相对就会小很多，以此满足系统的性能需求。值得注意的是，系统分片的策略有很多，例如常见的有以下几种：&lt;/p&gt;  &lt;ul&gt;   &lt;li&gt;根据ID特征：例如对记录的ID取模，得到的结果是几，那么这条记录就放在编号为几的数据分区上。 &lt;/li&gt;    &lt;li&gt;根据时间范围：例如前100万个用户数据在第1个分区中，第二个100万用户数据放在第2个分区中。 &lt;/li&gt;    &lt;li&gt;基于检索表：根据ID先去一个表内找到它所在的分区，然后再去目标分区进行查找。 &lt;/li&gt;    &lt;li&gt;…… &lt;/li&gt; &lt;/ul&gt;  &lt;p&gt;在这些数据分片策略之中没有哪个有绝对的优势，选择哪种策略完全是根据系统的业务或是数据特征来确定的。值得强调的是：数据分片不是银弹，它对系统的性能和&lt;a href="http://en.wikipedia.org/wiki/Scalability"&gt;伸缩性（Scalability）&lt;/a&gt;带来一定好处的同时，也会对系统开发带来许多复杂度。例如，有两条记录分别处在不同的服务器上，那么如果有一个业务是为它们建立一个“关联”，那么很可能表示“关联”的记录就必须在两个分区内各放一条。另外，如果您重视数据的完整性，那么跨数据分区的事务又立即变成了性能杀手。最后，如果有一些需要进行全局查找的业务，光有数据分片策略也很难对系统性能带来什么优势。&lt;/p&gt;  &lt;p&gt;数据分片虽然重要，但在使用之前一定要三思而后行。一旦踏上这艘贼船，往往不成功便成仁，很难回头。在我的经验里，一个滥用数据分片策略而事倍功半的项目给我留下了非常深刻的印象（当然也有成功的啦），因此目前我对待数据分片策略变得愈发谨慎。&lt;/p&gt;  &lt;p&gt;那么现在，我们便来讨论一种比较常见的数据分片策略。&lt;/p&gt;  &lt;h1&gt;策略描述&lt;/h1&gt;  &lt;p&gt;这里我先描述一个极其简单的业务：&lt;/p&gt;  &lt;ol&gt;   &lt;li&gt;系统中有用户，用户可以发表文章，文章会有评论 &lt;/li&gt;    &lt;li&gt;可以根据用户查找文章 &lt;/li&gt;    &lt;li&gt;可以根据文章查找评论 &lt;/li&gt; &lt;/ol&gt;  &lt;p&gt;那么，如果我要对这样一个系统进行数据分片又该怎么做呢？这里我们可以使用上面提到的第一种方式，即对记录的ID取模，并根据结果选择数据所在的分区。根据后两条业务中描述的查询要求，我们会为分区策略补充这样的规则：&lt;/p&gt;  &lt;ul&gt;   &lt;li&gt;某个用户的所有文章，与这个用户处在同一数据分区内。 &lt;/li&gt;    &lt;li&gt;某篇文章的所有评论，与这篇文章处在用一数据分区内。 &lt;/li&gt; &lt;/ul&gt;  &lt;p&gt;您可能会说，似乎只要保证“相同用户文章在同一个数据分区内”就行了，不是吗？没错，不过我这里让文章和用户在同一个分区内，也是为了方便许多额外的操作（例如在关系数据库中进行连接）。那么假设我们有4个数据分区，那么它们内部的条目可能便是：&lt;/p&gt;  &lt;table border="1" cellspacing="0" cellpadding="5"&gt;&lt;tbody&gt;     &lt;tr style="font-weight: bold"&gt;       &lt;td&gt;分区0&lt;/td&gt;        &lt;td&gt;分区1&lt;/td&gt;     &lt;/tr&gt;      &lt;tr&gt;       &lt;td&gt;         &lt;ul&gt;           &lt;li&gt;User 4              &lt;ul&gt;               &lt;li&gt;Article 8 &lt;/li&gt;                &lt;li&gt;Article 12                  &lt;ul&gt;                   &lt;li&gt;Comment 4 &lt;/li&gt;                    &lt;li&gt;Comment 16 &lt;/li&gt;                 &lt;/ul&gt;               &lt;/li&gt;             &lt;/ul&gt;           &lt;/li&gt;            &lt;li&gt;User 12              &lt;ul&gt;               &lt;li&gt;Article 4 &lt;/li&gt;             &lt;/ul&gt;           &lt;/li&gt;         &lt;/ul&gt;       &lt;/td&gt;        &lt;td&gt;         &lt;ul&gt;           &lt;li&gt;User 1              &lt;ul&gt;               &lt;li&gt;Article 5 &lt;/li&gt;                &lt;li&gt;Article 9                  &lt;ul&gt;                   &lt;li&gt;Comment 13 &lt;/li&gt;                    &lt;li&gt;Comment 17 &lt;/li&gt;                 &lt;/ul&gt;               &lt;/li&gt;             &lt;/ul&gt;           &lt;/li&gt;            &lt;li&gt;User 5              &lt;ul&gt;               &lt;li&gt;Article 13 &lt;/li&gt;             &lt;/ul&gt;           &lt;/li&gt;         &lt;/ul&gt;       &lt;/td&gt;     &lt;/tr&gt;      &lt;tr style="font-weight: bold"&gt;       &lt;td&gt;分区2&lt;/td&gt;        &lt;td&gt;分区3&lt;/td&gt;     &lt;/tr&gt;      &lt;tr&gt;       &lt;td&gt;         &lt;ul&gt;           &lt;li&gt;User 2              &lt;ul&gt;               &lt;li&gt;Article 10 &lt;/li&gt;                &lt;li&gt;Article 14                  &lt;ul&gt;                   &lt;li&gt;Comment 6 &lt;/li&gt;                    &lt;li&gt;Comment 10 &lt;/li&gt;                 &lt;/ul&gt;               &lt;/li&gt;             &lt;/ul&gt;           &lt;/li&gt;            &lt;li&gt;User 10              &lt;ul&gt;               &lt;li&gt;Article 4 &lt;/li&gt;             &lt;/ul&gt;           &lt;/li&gt;         &lt;/ul&gt;       &lt;/td&gt;        &lt;td&gt;         &lt;ul&gt;           &lt;li&gt;User 7              &lt;ul&gt;               &lt;li&gt;Article 7 &lt;/li&gt;                &lt;li&gt;Article 11                  &lt;ul&gt;                   &lt;li&gt;Comment 3 &lt;/li&gt;                    &lt;li&gt;Comment 15 &lt;/li&gt;                 &lt;/ul&gt;               &lt;/li&gt;             &lt;/ul&gt;           &lt;/li&gt;            &lt;li&gt;User 11              &lt;ul&gt;               &lt;li&gt;Article 4 &lt;/li&gt;             &lt;/ul&gt;           &lt;/li&gt;         &lt;/ul&gt;       &lt;/td&gt;     &lt;/tr&gt;   &lt;/tbody&gt;&lt;/table&gt;  &lt;p&gt;在ID为0的分区中，所有对象的ID模4均为0，其他分区里的对象也有这样的规律。那么好，在实际应用中，如果我们需要查找“ID为2的用户”，便去第2分区搜索便是；如果要查找“ID为8的文章的所有评论”那么也只要去第0分区进行一次查询即可。既然查询不成问题，那么我们该如何添加新记录呢？其实这也不难，只要：&lt;/p&gt;  &lt;ul&gt;   &lt;li&gt;添加新用户时，随机选择一个数据分区 &lt;/li&gt;    &lt;li&gt;添加新文章时，选择文章作者所在分区（可根据Article的UserID求模得到） &lt;/li&gt;    &lt;li&gt;添加新评论时，选择文章所在分区（可根据Comment的ArticleID求模得到） &lt;/li&gt; &lt;/ul&gt;  &lt;p&gt;但是，我们又如何保证新纪录的ID正好满足我们的分区规律？例如我们向第3分区添加的新数据，则它的ID必须是3、7、11等等。以前，我们可能会使用数据库的自增列作为ID的值，但这似乎不能满足我们“取模”的要求。以前我们可能还会使用GUID，但是我们如何生成一个“被4模于3”的GUID呢？其实我们还是可以使用自增ID来解决这个问题，只不过需要进行一些简单的设置。例如在SQL Server中，默认的自增ID属性为IDENTITY(1, 1)，表示ID从1开始，以1为间距自动增长。于是我们在创建数据分区的时候，每个自增列的属性则可以设置为：&lt;/p&gt;  &lt;ul&gt;   &lt;li&gt;分区0：IDENTITY(4, 4) &lt;/li&gt;    &lt;li&gt;分区1：IDENTITY(1, 4) &lt;/li&gt;    &lt;li&gt;分区2：IDENTITY(2, 4) &lt;/li&gt;    &lt;li&gt;分区3：IDENTITY(3, 4) &lt;/li&gt; &lt;/ul&gt;  &lt;p&gt;这样，ID方面的问题便交由数据库来关心吧，我们的使用方式和以前并没有什么区别。&lt;/p&gt;  &lt;h1&gt;缺陷&lt;/h1&gt;  &lt;p&gt;那么这个数据分片策略有什么缺陷呢？当然缺陷还是有很多啦，只是大多数问题可能还是要和业务放在一起考虑时才会凸显出来。不过有一个问题倒和业务关系不大：如果数据继续增长，单个数据分区的数据量也超标了，怎么办？&lt;/p&gt;  &lt;p&gt;自然，继续拆分咯。那么我们使用什么分区规则呢？和原先一致吗？我们举个例子便知。假设我们原有4个分区，有一个ID为1的用户落在第1分区里，他的文章也都在这个分区里，ID分别是1、5、9、13、17等等。于是在某一天，我们需要将分区数量提高到5个（财力有限，一台一台来吧），在重新计算每篇文章所在的分区之后，我们忽然发现：&lt;/p&gt;  &lt;ul&gt;   &lt;li&gt;ID为1的文章，模5余1，处在分区1。 &lt;/li&gt;    &lt;li&gt;ID为5的文章，模5余0，处在分区0。 &lt;/li&gt;    &lt;li&gt;ID为9的文章，模5余4，处在分区4。 &lt;/li&gt;    &lt;li&gt;ID为13的文章，模5余3，处在分区3。 &lt;/li&gt;    &lt;li&gt;ID为17的文章，模5余2，处在分区2。 &lt;/li&gt; &lt;/ul&gt;  &lt;p&gt;呼，5个分区都齐了！这说明，如果我们保持记录原来的ID不变，是没有办法直接使用之前的分区规则——无论您扩展成几个分区，（即便是从4个到8个）也只能“缓解”也不能“解决”这个情况。那么这时候该如何是好呢？例如，我们可以重新分配记录，改变原有ID，只是这么做会产生一个问题，便是外部URL可能也会随着ID一起改变，这样对SEO的折损很大。为此，我们可以制作一个查询表：例如在查询小于1234567的ID时（这是“老系统”的最大ID），假设是100，则根据查询表得知这条记录的新ID为7654321，再以此去数据源进行查找。解决这类问题的方法还有几种，但无论怎么做都会对新系统带来额外的复杂度。而且，一次扩展也罢，如果以后还要有所扩展呢？&lt;/p&gt; &lt;a href="http://img.zhaojie.me/blog/consistent-hashing.png" target="_blank"&gt;&lt;img class="floatRight" alt="consistent-hahsing" src="http://img.zhaojie.me/blog/consistent-hashing.png" width="250" /&gt;&lt;/a&gt;   &lt;p&gt;有朋友可能会说，取模自然会带来这样的问题，那么为什么不用&lt;a href="http://en.wikipedia.org/wiki/Consistent_hashing"&gt;一致性哈希（Consistent Hash）&lt;/a&gt;呢？现在一致性哈希是个很流行的东西，和Memcached一样，如果不用上就会被一些高级架构师所鄙视。不过在这里一致性哈希也不能解决问题。一致性哈希的目的，是希望“在增加服务器的时候降低数据移动规模，让尽可能多的数据保留在原有的服务器”上。而我们现在的问题却是“在增加服务器的时候，让特征相同的数据同样放在一起”。两个目标不同，这并不是一致性哈希的应用场景。&lt;/p&gt;  &lt;p&gt;我在以前的一个项目中曾经用过这样的方法：根据对访问量与数据量的预估，我们认为使用最多24个分区便一定可以满足性能要求（为什么是24个？因为它能被许多数字整除）。于是，从项目第一次在生产环境中部署时便创建了24个数据分区，只不过一开始只用了2台服务器，每台服务器放置12个数据分区。待以后需要扩展时，则将数据分区均匀地迁移到新的服务器上即可。我们团队当时便是用这种方法避免尴尬的数据分配问题。&lt;/p&gt;  &lt;p&gt;没错，数据分区的数目是个限制，但您真认为，24个数据分区还是无法满足您的项目需求吗？要知道，需要用上24个数据分区的项目，一般来说本身已经有充分的时间和经济实力进行架构上的重大调整（也该调整了，几乎没有什么架构可以满足各种数据规模的需求）。此外，无论是系统优化还是数据分片都可以同时运用其他手段。&lt;/p&gt;  &lt;p&gt;不过，我们目前还是想办法解决这个问题吧。&lt;/p&gt;  &lt;h1&gt;策略改进&lt;/h1&gt;  &lt;p&gt;我们之所以会遇到上面这个问题，在于我们没有选择好合适的策略，这个策略把一些重要的“要求”给“具体化”了，导致“具体化”后的结果在外部条件改变的时候，却无法重新满足原有的“要求”。还是以前面的案例来说明问题，其实我们“要求”其实是：&lt;/p&gt;  &lt;ul&gt;   &lt;li&gt;某个用户的所有文章，与这个用户处在同一数据分区内。 &lt;/li&gt;    &lt;li&gt;某篇文章的所有评论，与这篇文章处在用一数据分区内。 &lt;/li&gt; &lt;/ul&gt;  &lt;p&gt;而我们“具体化”以后的结果却是：&lt;/p&gt;  &lt;ul&gt;   &lt;li&gt;某个用户的所有文章ID，与这个用户的ID模4后的余数相同。 &lt;/li&gt;    &lt;li&gt;某篇文章的所有评论ID，与这篇文章的ID模4后的余数相同。 &lt;/li&gt; &lt;/ul&gt;  &lt;p&gt;之所以能如此“具体化”，这是因为有“4个分区”这样的前提条件在，一旦这个前提条件发生了改变，则杯具无法避免。因此，我们在制定规则的时候，其实不应该把前提条件给过分的“具体化”——具体化可以，但不能过度，得留有一定空间（这个稍后再谈）。打个比方，还是前面的条件（XX和XX处在同一数据分区内），但我们换一种具体化的方式：&lt;/p&gt;  &lt;ul&gt;   &lt;li&gt;某个用户的所有文章ID的前缀，便是这个用户的ID。例如，ID为1的用户的所有文章，其ID便可能是1-A1、1-A2、1-A3…… &lt;/li&gt;    &lt;li&gt;某篇文章的所有评论ID，与这个文章的ID使用相同前缀。例如，ID为3-A1的文章的所有评论，其ID便可能是3-C1、3-C2、3-C3…… &lt;/li&gt; &lt;/ul&gt;  &lt;p&gt;使用这个策略，我们便可以保证与某个用户相关的“所有数据”都共享相同的“特征”（ID的前缀都相同），然后我们便可以根据这个特征来选择分区——例如，还是以“取模”的方式。此时，我们已经确保了“相同分区内的所有数据都具备相同的特征”，即便分区数量有所调整，我们也只需要根据特征重新计算分区即可，影响不大。而以前为什么不行？因为“模4的余数”只是“结果”而不是“特征”，这里的“特征”应该是“追本溯源后的用户ID相同”，而这一点已经体现在新的策略中了。&lt;/p&gt;  &lt;p&gt;还是通过图示来说明问题吧。假设原有4个分区，使用“取模”的策略：&lt;/p&gt;  &lt;table border="1" cellspacing="0" cellpadding="5"&gt;&lt;tbody&gt;     &lt;tr style="font-weight: bold"&gt;       &lt;td&gt;分区0&lt;/td&gt;        &lt;td&gt;分区1&lt;/td&gt;     &lt;/tr&gt;      &lt;tr&gt;       &lt;td&gt;         &lt;ul&gt;           &lt;li&gt;User 4              &lt;ul&gt;               &lt;li&gt;Article 4-A1 &lt;/li&gt;                &lt;li&gt;Article 4-A2                  &lt;ul&gt;                   &lt;li&gt;Comment 4-C1 &lt;/li&gt;                    &lt;li&gt;Comment 4-C2 &lt;/li&gt;                 &lt;/ul&gt;               &lt;/li&gt;             &lt;/ul&gt;           &lt;/li&gt;            &lt;li&gt;User 12              &lt;ul&gt;               &lt;li&gt;Article 12-A3 &lt;/li&gt;             &lt;/ul&gt;           &lt;/li&gt;         &lt;/ul&gt;       &lt;/td&gt;        &lt;td&gt;         &lt;ul&gt;           &lt;li&gt;User 1              &lt;ul&gt;               &lt;li&gt;Article 1-A4 &lt;/li&gt;                &lt;li&gt;Article 1-A5                  &lt;ul&gt;                   &lt;li&gt;Comment 1-C3 &lt;/li&gt;                    &lt;li&gt;Comment 1-C4 &lt;/li&gt;                 &lt;/ul&gt;               &lt;/li&gt;             &lt;/ul&gt;           &lt;/li&gt;            &lt;li&gt;User 5              &lt;ul&gt;               &lt;li&gt;Article 5-A6 &lt;/li&gt;             &lt;/ul&gt;           &lt;/li&gt;         &lt;/ul&gt;       &lt;/td&gt;     &lt;/tr&gt;      &lt;tr style="font-weight: bold"&gt;       &lt;td&gt;分区2&lt;/td&gt;        &lt;td&gt;分区3&lt;/td&gt;     &lt;/tr&gt;      &lt;tr&gt;       &lt;td&gt;         &lt;ul&gt;           &lt;li&gt;User 2              &lt;ul&gt;               &lt;li&gt;Article 2-A7 &lt;/li&gt;                &lt;li&gt;Article 2-A8                  &lt;ul&gt;                   &lt;li&gt;Comment 2-C5 &lt;/li&gt;                    &lt;li&gt;Comment 2-C6 &lt;/li&gt;                 &lt;/ul&gt;               &lt;/li&gt;             &lt;/ul&gt;           &lt;/li&gt;            &lt;li&gt;User 10              &lt;ul&gt;               &lt;li&gt;Article 10-A9 &lt;/li&gt;             &lt;/ul&gt;           &lt;/li&gt;         &lt;/ul&gt;       &lt;/td&gt;        &lt;td&gt;         &lt;ul&gt;           &lt;li&gt;User 7              &lt;ul&gt;               &lt;li&gt;Article 7-A10 &lt;/li&gt;                &lt;li&gt;Article 7-A11                  &lt;ul&gt;                   &lt;li&gt;Comment 7-C7 &lt;/li&gt;                    &lt;li&gt;Comment 7-C8 &lt;/li&gt;                 &lt;/ul&gt;               &lt;/li&gt;             &lt;/ul&gt;           &lt;/li&gt;            &lt;li&gt;User 11              &lt;ul&gt;               &lt;li&gt;Article 11-A12 &lt;/li&gt;             &lt;/ul&gt;           &lt;/li&gt;         &lt;/ul&gt;       &lt;/td&gt;     &lt;/tr&gt;   &lt;/tbody&gt;&lt;/table&gt;  &lt;p&gt;当分区数量调整为5个之后（为了避免分区3空缺，我又补充了一些对象）：&lt;/p&gt;  &lt;table border="1" cellspacing="0" cellpadding="5"&gt;&lt;tbody&gt;     &lt;tr style="font-weight: bold"&gt;       &lt;td&gt;分区0&lt;/td&gt;        &lt;td&gt;分区1&lt;/td&gt;     &lt;/tr&gt;      &lt;tr&gt;       &lt;td&gt;         &lt;ul&gt;           &lt;li&gt;User 10&lt;/li&gt;            &lt;ul&gt;             &lt;li&gt;Article 10-A9&lt;/li&gt;           &lt;/ul&gt;            &lt;li&gt;User 5&lt;/li&gt;            &lt;ul&gt;             &lt;li&gt;Article 5-A6&lt;/li&gt;           &lt;/ul&gt;         &lt;/ul&gt;       &lt;/td&gt;        &lt;td&gt;         &lt;ul&gt;           &lt;li&gt;User 1 &lt;/li&gt;            &lt;ul&gt;             &lt;li&gt;Article 1-A4 &lt;/li&gt;              &lt;li&gt;Article 1-A5 &lt;/li&gt;              &lt;ul&gt;               &lt;li&gt;Comment 1-C3 &lt;/li&gt;                &lt;li&gt;Comment 1-C4&lt;/li&gt;             &lt;/ul&gt;           &lt;/ul&gt;            &lt;li&gt;User 11&lt;/li&gt;            &lt;ul&gt;             &lt;li&gt;Article 11-A12&lt;/li&gt;           &lt;/ul&gt;         &lt;/ul&gt;       &lt;/td&gt;     &lt;/tr&gt;      &lt;tr style="font-weight: bold"&gt;       &lt;td&gt;分区2&lt;/td&gt;        &lt;td&gt;分区3&lt;/td&gt;     &lt;/tr&gt;      &lt;tr&gt;       &lt;td&gt;         &lt;ul&gt;           &lt;li&gt;User 2              &lt;ul&gt;               &lt;li&gt;Article 2-A7 &lt;/li&gt;                &lt;li&gt;Article 2-A8                  &lt;ul&gt;                   &lt;li&gt;Comment 2-C5 &lt;/li&gt;                    &lt;li&gt;Comment 2-C6 &lt;/li&gt;                 &lt;/ul&gt;               &lt;/li&gt;             &lt;/ul&gt;           &lt;/li&gt;            &lt;li&gt;User 12              &lt;ul&gt;               &lt;li&gt;Article 12-A3&lt;/li&gt;             &lt;/ul&gt;           &lt;/li&gt;            &lt;li&gt;User 7&lt;/li&gt;            &lt;ul&gt;             &lt;li&gt;Article 7-A10&lt;/li&gt;              &lt;li&gt;Article 7-A11&lt;/li&gt;              &lt;ul&gt;               &lt;li&gt;Comment 7-C7&lt;/li&gt;                &lt;li&gt;Comment 7-C8 &lt;/li&gt;             &lt;/ul&gt;           &lt;/ul&gt;         &lt;/ul&gt;       &lt;/td&gt;        &lt;td&gt;         &lt;ul&gt;           &lt;li&gt;User 8 &lt;/li&gt;            &lt;ul&gt;             &lt;li&gt;Article 8-A12 &lt;/li&gt;              &lt;li&gt;Article 8-A13 &lt;/li&gt;              &lt;ul&gt;               &lt;li&gt;Comment 8-C9 &lt;/li&gt;                &lt;li&gt;Comment 7-C10 &lt;/li&gt;             &lt;/ul&gt;           &lt;/ul&gt;         &lt;/ul&gt;       &lt;/td&gt;     &lt;/tr&gt;      &lt;tr style="font-weight: bold"&gt;       &lt;td&gt;分区4&lt;/td&gt;        &lt;td&gt;&amp;#160;&lt;/td&gt;     &lt;/tr&gt;      &lt;tr&gt;       &lt;td&gt;         &lt;ul&gt;           &lt;li&gt;User 4              &lt;ul&gt;               &lt;li&gt;Article 4-A1 &lt;/li&gt;                &lt;li&gt;Article 4-A2                  &lt;ul&gt;                   &lt;li&gt;Comment 4-C1 &lt;/li&gt;                    &lt;li&gt;Comment 4-C2 &lt;/li&gt;                 &lt;/ul&gt;               &lt;/li&gt;             &lt;/ul&gt;           &lt;/li&gt;         &lt;/ul&gt;       &lt;/td&gt;        &lt;td&gt;&amp;#160;&lt;/td&gt;     &lt;/tr&gt;   &lt;/tbody&gt;&lt;/table&gt;  &lt;p&gt;是不是很合理？&lt;/p&gt;  &lt;p&gt;值得一提的是，只要满足了“特征”这个要求，其实选择分区的方式并没有什么限制。例如，我们可以不用“取模”的方式，而是使用“一致性哈希”——没错，这里就是一致性哈希的使用场景了。在利用“一致性哈希”来选择分区之后，在添加服务器的情况下便可以相对减少数据的迁移数量了。&lt;/p&gt;  &lt;p&gt;当然，在实现时还可以运用一些技巧。例如，我们的特征并非一定要“把用户ID作为前缀”——毕竟用户ID可能比较长，作为ID前缀还真有些难看（请想象把GUID作为ID前缀，再加上另一个GUID作为ID主体的情景）。此时，我们可以把前提条件先进行一定程度的“具体化”（但就像之前提到的，不能过度），例如我们可以把用户ID先进行取模，可能是1000万，便可以得到一个落在较大区间范围内的数字。然后，再把这个数字作BASE64编码，一下子前缀就缩小为4个字符以内了。而且，1000万这个区间范围，无论是使用取模还是一致性哈希的方式来选择分区都非常可行，一般不会造成什么问题。&lt;/p&gt;  &lt;h1&gt;总结&lt;/h1&gt;  &lt;p&gt;数据分片是系统优化的常用设计方式之一。正如前文所说的那样，数据分片的做法很多，本文提到的方式只是其中一种方式。这种根据ID特征的分片方式比较容易遇到的问题之一，便是在数据分区数量改变时造成的规则冲突，这也正是我这篇文章所讨论的主要内容。从这个角度看来，其他一些分片方式，如创建时间也好，查找表也罢，这样的问题反而不太常见。如果您有这方面的经验或是疑惑，也欢迎与我进行交流。&lt;/p&gt;  &lt;p&gt;现在Web 2.0网站越来越热门了，此类项目的数据量也越来越大，从近几年的讨论形式可以看出，越来越多的人在强调什么大规模、高性能、或是海量数据。然后，似乎每个人都会横向切分、纵向切分、缓存、分离。我猜，再接下来，估计又会有许多人以用关系型数据库为耻了吧？但是，想想这样的问题：博客园和JavaEye都是国内技术社区的翘楚，它们都只用了1台数据库服务器。&lt;a href="http://stackoverflow.com"&gt;StackOverflow&lt;/a&gt;是&lt;a href="http://www.joelonsoftware.com/items/2009/12/13.html"&gt;世界上最大的编程网站&lt;/a&gt;（它是使用ASP.NET MVC写的，兄弟们记住这个经典案例吧），似乎也只用了1台还是2台数据库服务器（可能配置比较高）及SQL Server。因此，即便是单台服务器，即便是使用关系型数据库，它在性能方面的潜力也是非常之高的。&lt;/p&gt;  &lt;p&gt;因此，数据分片应该只在需要的时候才做，因为它带来的复杂度会比中心存储的方式高出很多。这带来的结果是，可能您的应用程序还没有用足架构的能力就已经失败了，这样各种投资也已经浪费了。假如您一开始用最简单的方式去做，可能很快会带来成长所需要空间及资源，此时再做更多投资进行架构优化也不迟——&lt;a href="http://www.infoq.com/cn/presentations/hongqn-douban"&gt;架构不是一蹴而就，而是演变得来的&lt;/a&gt;。当然，第一次投入多少复杂度是个需要权衡的东西，这也是考验架构师能力的地方。架构不是空中楼阁，而是各种真实资源调配的结果。&lt;/p&gt;</description>
      <comments>http://blog.zhaojie.me/2010/03/sharding-by-id-characteristic.html#comments</comments>
      <pubDate>Tue, 09 Mar 2010 13:51:00 GMT</pubDate>
      <lastBuildDate>Tue, 09 Mar 2010 13:51:00 GMT</lastBuildDate>
    </item>
    <item>
      <author>jeffz@live.com (老赵)</author>
      <category domain="http://blog.zhaojie.me/asp-net/">ASP.NET</category>
      <category domain="http://blog.zhaojie.me/architecture/">架构设计</category>
      <title>浅谈Route组件的设计思考与模式</title>
      <link>http://blog.zhaojie.me/2009/10/aspnet-routing-design-ideas-and-patterns.html</link>
      <guid>http://blog.zhaojie.me/2009/10/aspnet-routing-design-ideas-and-patterns.html</guid>
      <description>&lt;p&gt;Route组件虽然可以说是ASP.NET的“门户”，不过至今为止似乎都被微软当作是二等公民。可能是由于自带的Route类功能已经太强，微软官方或社区内都不太关注RouteBase的扩展。不过有一点是正确的，那就是在大部分情况下的确没有必要去扩展RouteBase。事实上，我构建过不少RouteBase类，不过除了&lt;a href="http://blog.zhaojie.me/2009/08/url-routing-with-domain.html"&gt;DomainRoute&lt;/a&gt;之外，其余的都被我放弃了，例如在大半年前写的《&lt;a href="http://blog.zhaojie.me/2009/03/fully-leverage-url-routing.html"&gt;请别埋没了URL Routing&lt;/a&gt;》中所提供的FormatRoute，在&lt;a href="http://MvcPatch.codeplex.com"&gt;MvcPatch&lt;/a&gt;中也已经有了更好的替代品（过几天便会谈到这一点）。&lt;/p&gt; &lt;p&gt;RouteBase&lt;a href="http://blog.zhaojie.me/2009/09/things-about-aspnet-routing.html"&gt;职责明确&lt;/a&gt;：从请求中获取数据，及根据数据生成虚拟路径。它只有两个方法：GetRouteData和GetVirtualPath，扩展起来非常容易，各种“模式”均可以体现出来。例如DomainRoute和FormatRoute都是使用了&lt;a href="http://en.wikipedia.org/wiki/Decorator_pattern"&gt;装饰器模式&lt;/a&gt;，在内部RouteBase的GetRouteData或GetVirtualPath方法的“前后”再加上一些逻辑（例如DomainRoute中的域名匹配或生成）。有趣的是，在几个月前我还写过一个InterceptRoute类：&lt;/p&gt;&lt;pre class="code"&gt;&lt;span style="color: blue"&gt;public class &lt;/span&gt;&lt;span style="color: #2b91af"&gt;InterceptRoute &lt;/span&gt;: &lt;span style="color: #2b91af"&gt;RouteBase
&lt;/span&gt;{
    &lt;span style="color: blue"&gt;public &lt;/span&gt;InterceptRoute(&lt;span style="color: #2b91af"&gt;RouteBase &lt;/span&gt;innerRoute, &lt;span style="color: #2b91af"&gt;IList&lt;/span&gt;&amp;lt;&lt;span style="color: #2b91af"&gt;IRouteInterceptor&lt;/span&gt;&amp;gt; interceptors)
    {
        &lt;span style="color: blue"&gt;this&lt;/span&gt;.InnerRoute = innerRoute;
        &lt;span style="color: blue"&gt;this&lt;/span&gt;.Interceptors = &lt;span style="color: blue"&gt;new &lt;/span&gt;&lt;span style="color: #2b91af"&gt;InterceptorCollection&lt;/span&gt;(interceptors);
    }

    &lt;span style="color: blue"&gt;public &lt;/span&gt;&lt;span style="color: #2b91af"&gt;RouteBase &lt;/span&gt;InnerRoute { &lt;span style="color: blue"&gt;get&lt;/span&gt;; &lt;span style="color: blue"&gt;private set&lt;/span&gt;; }

    &lt;span style="color: blue"&gt;public &lt;/span&gt;&lt;span style="color: #2b91af"&gt;InterceptorCollection &lt;/span&gt;Interceptors { &lt;span style="color: blue"&gt;get&lt;/span&gt;; &lt;span style="color: blue"&gt;private set&lt;/span&gt;; }

    ...
}&lt;/pre&gt;
&lt;p&gt;在很多时候，能够像一个组件中插入“横切”的逻辑总是很有用的（例如昨天刚提的&lt;a href="http://blog.zhaojie.me/2009/10/my-view-of-nhibernate-4-interceptor.html"&gt;NHibernate Interceptor&lt;/a&gt;），而上面这个便是在Route规则的各方法前后插入各种逻辑。提供这个逻辑的便是IRouteInterceptor对象，它有四个方法：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;PreGetRouteData&lt;/li&gt;
&lt;li&gt;PostGetRouteData&lt;/li&gt;
&lt;li&gt;PreGetVirtualPath&lt;/li&gt;
&lt;li&gt;PostGetVirtualPath&lt;/li&gt;&lt;/ul&gt;
&lt;p&gt;从它们的名称上您也一定可以看得出它们是做什么的。从理论上来说，无论是DomainRoute还是FormatRoute，只要是为现有方法补充前/后置逻辑的扩展，都可以通过提供IRouteInterceptor来实现。不过我除了DomainRoute以外，还真没发现其他的使用环境。这个InterceptRoute似乎也是娱乐价值大于实际价值。因此就在这里一提，等以后忽然发现真有用了我们再拿出来遛遛。&lt;/p&gt;
&lt;p&gt;除了装饰器模式/InterceptRoute之外，我还曾经想过构建另一种“结构性”（如InterceptRoute一样，本身不提供实际用途）的Route扩展，那就是利用了&lt;a href="http://en.wikipedia.org/wiki/Composite_pattern"&gt;组合模式&lt;/a&gt;的Route规则。利用组合模式，我们可以将多个RouteBase对象聚合起来，并且在GetRouteData或GetVirtualPath的时候将职责委派给这些对象。事实上，它的职责就好似Routing框架本身所带的RouteCollection一样——当然，之前我们也谈过，RouteCollection的逻辑&lt;a href="http://blog.zhaojie.me/2009/09/things-about-aspnet-routing.html"&gt;并不那么单纯&lt;/a&gt;。&lt;/p&gt;
&lt;p&gt;假设我们已经有了这样一个CompositeRoute对象收集了一堆Route规则，那么什么时候会需要这样的场景呢？其实DomainRoute就&lt;font color="#ff0000"&gt;可以&lt;/font&gt;是这样的，因为“一个域名下有多个Route规则”简直是天经地义的事情。但其实事情并没有那么简单，个中原因便是我们昨天所谈论的“&lt;a href="http://blog.zhaojie.me/2009/10/name-of-route-with-mvcpatch.html"&gt;命名问题&lt;/a&gt;”。&lt;/p&gt;
&lt;p&gt;由于在配置Route规则的时候，我们要为每个Route提供一个名称——但是这个名称只是对RouteCollection才有效果，确切地说，只有RouteTable.Routes这个RouteCollection实例才会用到这一点。如此的话，使用CompositeRoute势必将原本能够有名称的多个Route规则捆绑在了一起，我们在生成URL的时候就无法通过名称定位到特定的Route上了。&lt;/p&gt;
&lt;p&gt;由于RouteCollection中释放接口有限（也不是开源的，这意味着我们无法改造它），这一点几乎无法通过自定义逻辑的方式来改进。因此在我看来，CompositeRoute几乎无法用在任何场景上——DomainRoute当然也不会使用这种设计方式了。&lt;/p&gt;</description>
      <comments>http://blog.zhaojie.me/2009/10/aspnet-routing-design-ideas-and-patterns.html#comments</comments>
      <pubDate>Wed, 14 Oct 2009 01:46:00 GMT</pubDate>
      <lastBuildDate>Wed, 14 Oct 2009 01:46:00 GMT</lastBuildDate>
    </item>
    <item>
      <author>jeffz@live.com (老赵)</author>
      <category domain="http://blog.zhaojie.me/practice/">实践优化</category>
      <category domain="http://blog.zhaojie.me/architecture/">架构设计</category>
      <title>我对NHibernate的感受（4）：令人欣喜的Interceptor机制</title>
      <link>http://blog.zhaojie.me/2009/10/my-view-of-nhibernate-4-interceptor.html</link>
      <guid>http://blog.zhaojie.me/2009/10/my-view-of-nhibernate-4-interceptor.html</guid>
      <description>&lt;p&gt;之前谈了NHibernate的几个方面，似乎抱怨的居多，不过这次我想谈一下我对Interceptor的感受，则基本上都是好话了。这并不一定是说Interceptor设计的又多么好（事实上它使用起来还是挺麻烦的），但是这的确也是我认为NHibernate超越LINQ to SQL，尤其是Entity Framework的又一个重要方面——因为Entity Framework本身也已经不差了。更重要的是，Interceptor机制让我得以实现我“理想中的”数据访问功能。当然现在只是浅尝辄止一番，我打算以后再慢慢地，详细地谈谈我所满意的“数据访问层”设计。&lt;/p&gt; &lt;p&gt;Interceptor的作用是为NHIbernate中的Session（如LINQ to SQL中的DataContext）增加一个“拦截器”，这个拦截器会捕获到Session各个阶段所发生的事情，并且有机会访问到它们所牵涉到的数据。例如：&lt;/p&gt; &lt;ul&gt; &lt;li&gt;OnLoad：当前Session加载了哪些对象  &lt;li&gt;OnDelete：当前Session删除了哪些对象  &lt;li&gt;OnSave：Session保存了哪些对象  &lt;li&gt;PostFlush：当前Session的Flush已经完成了&lt;/li&gt;&lt;/ul&gt; &lt;p&gt;关于Interceptor功能，&lt;a href="http://nhforge.org/doc/nh/en/index.html"&gt;NHibernate的文档上只是一笔带过&lt;/a&gt;，更详细的信息可以参考&lt;a href="https://www.hibernate.org/hib_docs/v3/api/org/hibernate/Interceptor.html"&gt;Hibernate的API说明&lt;/a&gt;。由于Interceptor可以记录到Session中所经过的所有对象，因此它可以做的事情就很多了。例如已经被人写滥的“日志记录”或“审查（Audit）”，但好像少有人把它真正用在数据访问的功能上。我了解Interceptor之后感觉非常兴奋，因为终于有人为我想要实现的功能做好铺垫了。这个功能便是“&lt;font color="#ff0000"&gt;结合其它数据访问机制&lt;/font&gt;”。&lt;/p&gt; &lt;p&gt;说到数据访问层，大家肯定知道它的职责是“从数据源读写数据”。在很长一段时间内，这个数据源基本上就是关系型数据库，无论是商业的SQL Server，Oracle还是开源的MySQL，PostgreSQL，万变不离其宗。于是有人提出了SqlHelper，Data Access Block这样的数据库读取辅助工具、iBatis这样的SQL-对象映射工具（我不认为它是ORM）、还有NHibernate、LINQ to SQL这样的ORM框架。但是无论是什么工具，无论怎么访问，数据访问层作的事情也无非是SQL、SQL、SQL，然后再把得到的数据集转化为内存中的对象。&lt;/p&gt; &lt;p&gt;但是到了如今的时代，数据访问层所负责的数据源已经远远不止这些了。例如，&lt;a href="http://www.longker.org/"&gt;许超前&lt;/a&gt;在博客上介绍了手机之家的数据访问层功能，这是其中一幅截图：&lt;/p&gt;&lt;a href="http://farm3.static.flickr.com/2586/3909558691_8ed5e42636_o.gif"&gt;&lt;img src="http://farm3.static.flickr.com/2586/3909558691_8ed5e42636_o.gif" width="450"&gt;&lt;/a&gt;  &lt;p&gt;这是个目前比较典型的数据访问层功能，它除了访问关系型数据库之外，可能还需要访问异步消息队列（如MSMQ、ActiveMQ）、K/V存储（如Memcached，Tokyo Cabinet）或是其他文档型数据库（如MongoDB、CouchDB）。也就是说，我们在将数据存入关系型数据库的时候，可能还要添加一条异步消息，或是同步到其他存储方式中——而Interceptor便为我们提供了这样的可能。&lt;/p&gt; &lt;p&gt;例如，这是一个ArticleInterceptor的结构：&lt;/p&gt;&lt;pre class="code"&gt;&lt;span style="color: blue"&gt;public class &lt;/span&gt;&lt;span style="color: #2b91af"&gt;ArticleInterceptor &lt;/span&gt;: &lt;span style="color: #2b91af"&gt;EmptyInterceptor
&lt;/span&gt;{
    &lt;span style="color: blue"&gt;public override bool &lt;/span&gt;OnLoad(&lt;span style="color: blue"&gt;object &lt;/span&gt;entity, &lt;span style="color: blue"&gt;object &lt;/span&gt;id, ...)
    {
        &lt;span style="color: blue"&gt;var &lt;/span&gt;article = entity &lt;span style="color: blue"&gt;as &lt;/span&gt;&lt;span style="color: #2b91af"&gt;Article&lt;/span&gt;;
        &lt;span style="color: blue"&gt;if &lt;/span&gt;(article == &lt;span style="color: blue"&gt;null&lt;/span&gt;) &lt;span style="color: blue"&gt;return false&lt;/span&gt;;

        &lt;span style="color: green"&gt;// 记下所有加载的ArticleID

        &lt;/span&gt;&lt;span style="color: blue"&gt;return false&lt;/span&gt;;
    }

    &lt;span style="color: blue"&gt;public override void &lt;/span&gt;OnDelete(&lt;span style="color: blue"&gt;object &lt;/span&gt;entity, &lt;span style="color: blue"&gt;object &lt;/span&gt;id, ...)
    {
        &lt;span style="color: blue"&gt;var &lt;/span&gt;article = entity &lt;span style="color: blue"&gt;as &lt;/span&gt;&lt;span style="color: #2b91af"&gt;Article&lt;/span&gt;;
        &lt;span style="color: blue"&gt;if &lt;/span&gt;(article == &lt;span style="color: blue"&gt;null&lt;/span&gt;) &lt;span style="color: blue"&gt;return&lt;/span&gt;;

        &lt;span style="color: green"&gt;// 记下所有删除的ArticleID
    &lt;/span&gt;}

    &lt;span style="color: blue"&gt;public override void &lt;/span&gt;PostFlush(&lt;span style="color: #2b91af"&gt;ICollection &lt;/span&gt;entities)
    {
        &lt;span style="color: blue"&gt;foreach &lt;/span&gt;(&lt;span style="color: blue"&gt;var &lt;/span&gt;article &lt;span style="color: blue"&gt;in &lt;/span&gt;entities.OfType&amp;lt;&lt;span style="color: #2b91af"&gt;Article&lt;/span&gt;&amp;gt;())
        {
            &lt;span style="color: green"&gt;// 进行对比，将修改，创建或删除的Article内容提交至Lucene索引
        &lt;/span&gt;}
    }&lt;span style="color: green"&gt;
&lt;/span&gt;}
&lt;/pre&gt;&lt;a href="http://11011.net/software/vspaste"&gt;&lt;/a&gt;
&lt;p&gt;NHibernate的优点之一是“自动跟踪”对象状态，而Interceptor也给我们这样插一手的机会。利用如上的ArticleInterceptor，我们就可以知道Session中修改了哪些Article对象，并且在Flush操作之后同步至Lucene索引。当然，这只是一个示例（事实上Lucene索引只能接受单个线程的写），具体情况需要根据各自需求来进行改变。而其中更关键的问题，可能便是“事务”了。&lt;/p&gt;
&lt;p&gt;PostFlush会在Session的Flush操作完成之后调用，但是这时候当前数据库事务可能并没有完成——这可能是因为程序在使用NHibernate的时候选择了外部控制事务的方式，这个事务可能跨Session等等。于是，如果是像外部数据源的更新，它往往不会“卷入”当前的数据库事务（可能是做不到，也可能是故意避免分布式事务），因此如果它在PostFlush操作中就完成的写入，那么如果当前事务回滚之后，外部数据源的更新能一起撤销吗？&lt;/p&gt;
&lt;p&gt;不过，对于程序员来说，这些应该都不是问题。例如，我们可以在PostFlush之后“纪录”需要更新的内容，然后在整个事务已经确保成功的时候才写入外部数据源。总之，要根据不同项目的需求来确定。&lt;/p&gt;
&lt;p&gt;利用NHibernate的Interceptor，其实我们还可以在这方面做更多的文章。例如，我们可以把Article对象的Content数据放入K/V存储内，这样可以减少关系型数据库中表的每一行的大小，有利于性能的提高。然后在加载Article对象的时候，我们在Interceptor的OnLoad方法中可以再将Article的Content数据读回从K/V存储内读取回来。&lt;/p&gt;
&lt;p&gt;因此在我看来，我们使用Interceptor，完全可以将NHibernate这个ORM框架打造成一个Object-Any-Mapping工具，将对象与“任何”存储方式进行映射。在大约两个月前，我想，如果我真要构建一个这样的OAM框架，我又该怎么做呢？&lt;/p&gt;
&lt;p&gt;只不过，我想着想着最后放弃了。因为这样的框架实在过于复杂。因为在我的想法中，如果使用通用的OAM框架，需要能够支持多种数据源，并且可以将一个对象分为多个部分，分别放入不同的数据源中。在查询的时候，还可以根据不同数据源的特征选择查询方式（例如，全文查找去Lucene，根据ID获取数据则通过K/V存储，其他可能就是关系型数据库或是文档数据库了）——甚至于还会自动从不同数据源中获取数据后，在内存中作JOIN。&lt;/p&gt;
&lt;p&gt;这些都是实际开发过程中所需要的真实功能，但是一个像NHibernate这样的ORM框架已经如此复杂了，这样的OAM的“可行性”如何……我想大家也都有思考结果。虽然Hibernate也已经有一些通用的框架，如Hiberante Search集成了Lucene，Hibernate Sharding可以支持自动的数据库划分，但是它们的“通用性”真有Hibernate这样高吗？我对此表示怀疑。&lt;/p&gt;
&lt;p&gt;因此，就目前而言，我认为最可行的方式，可能就是基于NHibernate这样强有力的ORM框架打造的数据访问层，至于内部“在哪个时机选择什么样的数据操作方式”，还是由数据访问层本身根据自身需要来吧。由于不需要“通用”，这样的数据访问层可以变得相对简单很多。&lt;/p&gt;
&lt;p&gt;由于存储方式的不断出现，对合适的操作选择合适的存储方式已经是一个重要的课题，因此数据访问层的构建也已经越来越重要，它不仅仅是无聊的CRUD操作了——反而是对于一些项目由于没有复杂的逻辑，它的“业务逻辑层”会显得非常薄。您的数据访问层是如何构建的，能分享一下这方面的经验吗？&lt;/p&gt;
&lt;p&gt;在今后的文章中，我也会有更多展开的。&lt;/p&gt;
&lt;h1&gt;相关文章&lt;/h1&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="http://blog.zhaojie.me/2009/09/my-view-of-nhibernate-1-lazy-loading.html"&gt;我对NHibernate的感受（1）：对延迟加载方式的误解&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="http://blog.zhaojie.me/2009/09/my-view-of-nhibernate-2-virtually-everything.html"&gt;我对NHibernate的感受（2）：何必到处都virtual&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="http://blog.zhaojie.me/2009/10/my-view-of-nhibernate-3-collection-support.html"&gt;我对NHibernate的感受（3）：有些尴尬的集合支持&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;我对NHibernate的感受（4）：令人欣喜的Interceptor机制&lt;/li&gt;
&lt;/ul&gt;</description>
      <comments>http://blog.zhaojie.me/2009/10/my-view-of-nhibernate-4-interceptor.html#comments</comments>
      <pubDate>Tue, 13 Oct 2009 05:45:00 GMT</pubDate>
      <lastBuildDate>Tue, 13 Oct 2009 05:45:00 GMT</lastBuildDate>
    </item>
    <item>
      <author>jeffz@live.com (老赵)</author>
      <category domain="http://blog.zhaojie.me/practice/">实践优化</category>
      <category domain="http://blog.zhaojie.me/architecture/">架构设计</category>
      <title>检查几个程序集中的internal成员</title>
      <link>http://blog.zhaojie.me/2009/09/detect-internal-members-in-libraries.html</link>
      <guid>http://blog.zhaojie.me/2009/09/detect-internal-members-in-libraries.html</guid>
      <description>&lt;p&gt;两个星期前我写了一篇文章谈到一个现象（或是感觉）：我发现“&lt;a href="http://blog.zhaojie.me/2009/08/internal-member-is-bad-smell.html"&gt;类中的internal成员可能是一种坏味道&lt;/a&gt;”，原因在于违反了“&lt;a href="http://en.wikipedia.org/wiki/Single_responsibility_principle"&gt;单一职责&lt;/a&gt;”原则。然后谈了一般情况下遇到这种情况时一种&lt;a href="http://blog.zhaojie.me/2009/08/more-on-bad-smell-of-internal-members-in-public-classes.html"&gt;可用的重构方式&lt;/a&gt;之一。结果自然是有人同意有人反对。不过刚才我忽然想到，不如检查一下微软的框架中internal成员的情况吧。微软最近几个框架都公开的源代码，社区反响不错，应该较为值得参考。&lt;/p&gt; &lt;p&gt;首先我准备了这样一段代码：&lt;/p&gt;&lt;pre class="code"&gt;&lt;span style="color: blue"&gt;foreach &lt;/span&gt;(&lt;span style="color: blue"&gt;var &lt;/span&gt;m &lt;span style="color: blue"&gt;in &lt;/span&gt;Detect(&lt;span style="color: blue"&gt;typeof&lt;/span&gt;(&lt;span style="color: #2b91af"&gt;Route&lt;/span&gt;).Assembly))
{
    &lt;span style="color: #2b91af"&gt;Console&lt;/span&gt;.WriteLine(m.DeclaringType.Name + &lt;span style="color: #a31515"&gt;" - " &lt;/span&gt;+ m.Name);
}&lt;/pre&gt;
&lt;p&gt;这段代码会输出所有满足条件的方法，条件有三项：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;属于公开类（即GetExprotedTypes方法获得的类型）。 &lt;/li&gt;
&lt;li&gt;直接定义在类中的方法（而不是通过继承得来的）。 &lt;/li&gt;
&lt;li&gt;修饰符为internal（即代码中的IsAssembly条件）。&lt;/li&gt;&lt;/ol&gt;
&lt;p&gt;首先，我们来查看ASP.NET Routing（System.Web.Routing.dll）中的internal成员：&lt;/p&gt;&lt;pre class="code"&gt;&lt;span style="color: blue"&gt;foreach &lt;/span&gt;(&lt;span style="color: blue"&gt;var &lt;/span&gt;m &lt;span style="color: blue"&gt;in &lt;/span&gt;Detect(&lt;span style="color: blue"&gt;typeof&lt;/span&gt;(&lt;span style="color: #2b91af"&gt;Route&lt;/span&gt;).Assembly))
{
    &lt;span style="color: #2b91af"&gt;Console&lt;/span&gt;.WriteLine(m.Name);
}&lt;/pre&gt;
&lt;p&gt;其结果是：&lt;/p&gt;&lt;pre class="code"&gt;RequestContext - set_HttpContext
RequestContext - set_RouteData&lt;/pre&gt;
&lt;p&gt;也就是说，除了RequestContext中的两个属性的set方法，没有其他internal的成员。但是从.NET Reflector的代码分析来看，这两个方法根本没有被RequestContext以外的类型使用过：&lt;/p&gt;&lt;img src="http://img.zhaojie.me/blog/168980/o_RequestContext-Property-Internal-Set.png"&gt; 
&lt;p&gt;也就是说，即使我们把这两个成员设为private也没有任何问题。&lt;/p&gt;
&lt;p&gt;接下来看一个大一些的框架：ASP.NET MVC（System.Web.Mvc.dll）。结果如下：&lt;/p&gt;&lt;pre class="code"&gt;ValidateAntiForgeryTokenAttribute - get_Serializer
ValidateAntiForgeryTokenAttribute - set_Serializer
AjaxOptions - ToJavascriptString
HtmlHelper - get_Serializer
HtmlHelper - set_Serializer
HtmlHelper - EvalString
HtmlHelper - EvalBoolean
HtmlHelper - GetModelStateValue
HtmlHelper - RenderPartialInternal
ValueProviderDictionary - get_Dictionary
ReflectedActionDescriptor - get_DispatcherCache
ReflectedActionDescriptor - set_DispatcherCache
DefaultModelBinder - BindComplexElementalModel
DefaultModelBinder - BindComplexModel
DefaultModelBinder - BindSimpleModel
DefaultModelBinder - UpdateCollection
DefaultModelBinder - UpdateDictionary
OutputCacheAttribute - get_CacheSettings
Controller - get_RouteCollection
Controller - set_RouteCollection
ControllerActionInvoker - get_DescriptorCache
ControllerActionInvoker - set_DescriptorCache
MultiSelectList - GetListItems
RedirectToRouteResult - get_Routes
RedirectToRouteResult - set_Routes
DefaultControllerFactory - get_BuildManager
DefaultControllerFactory - set_BuildManager
DefaultControllerFactory - get_ControllerBuilder
DefaultControllerFactory - set_ControllerBuilder
DefaultControllerFactory - get_ControllerTypeCache
DefaultControllerFactory - set_ControllerTypeCache
MvcHandler - get_ControllerBuilder
MvcHandler - set_ControllerBuilder
ViewMasterPage - get_ViewPage
ViewUserControl - get_ViewPage
WebFormView - get_BuildManager
WebFormView - set_BuildManager
WebFormViewEngine - get_BuildManager
WebFormViewEngine - set_BuildManager&lt;/pre&gt;
&lt;p&gt;哗，好长一个列表。不过看到这里我觉得似乎有些不太对劲儿，为什么基本上都是些属性？因为一般来说，属性不代表一个类的“行为”，所以我打算忽略掉所有的属性。因此，我把Detect方法修改为：&lt;/p&gt;&lt;pre class="code"&gt;&lt;span style="color: blue"&gt;public static &lt;/span&gt;&lt;span style="color: #2b91af"&gt;IEnumerable&lt;/span&gt;&amp;lt;&lt;span style="color: #2b91af"&gt;MethodInfo&lt;/span&gt;&amp;gt; Detect(&lt;span style="color: #2b91af"&gt;Assembly &lt;/span&gt;assembly)
{
    &lt;span style="color: blue"&gt;foreach &lt;/span&gt;(&lt;span style="color: blue"&gt;var &lt;/span&gt;type &lt;span style="color: blue"&gt;in &lt;/span&gt;assembly.GetExportedTypes())
    {
        &lt;span style="color: blue"&gt;var &lt;/span&gt;methods = type.GetMethods(
            &lt;span style="color: #2b91af"&gt;BindingFlags&lt;/span&gt;.Instance |
            &lt;span style="color: #2b91af"&gt;BindingFlags&lt;/span&gt;.NonPublic |
            &lt;span style="color: #2b91af"&gt;BindingFlags&lt;/span&gt;.InvokeMethod |
            &lt;span style="color: #2b91af"&gt;BindingFlags&lt;/span&gt;.DeclaredOnly);

        &lt;span style="color: blue"&gt;foreach &lt;/span&gt;(&lt;span style="color: blue"&gt;var &lt;/span&gt;m &lt;span style="color: blue"&gt;in &lt;/span&gt;methods)
        {
            &lt;span style="color: blue"&gt;if &lt;/span&gt;(m.IsAssembly&lt;font color="#ff0000"&gt; &amp;amp;&amp;amp; !m.IsSpecialName&lt;/font&gt;)
            {
                &lt;span style="color: blue"&gt;yield return &lt;/span&gt;m;
            }
        }
    }
}&lt;/pre&gt;
&lt;p&gt;于是剩下的方法还有：&lt;/p&gt;&lt;pre class="code"&gt;AjaxOptions - ToJavascriptString
HtmlHelper - EvalString
HtmlHelper - EvalBoolean
HtmlHelper - GetModelStateValue
HtmlHelper - RenderPartialInternal
DefaultModelBinder - BindComplexElementalModel
DefaultModelBinder - BindComplexModel
DefaultModelBinder - BindSimpleModel
DefaultModelBinder - UpdateCollection
DefaultModelBinder - UpdateDictionary
MultiSelectList - GetListItems&lt;/pre&gt;
&lt;p&gt;再对ASP.NET AJAX（System.Web.Extensions.dll）运行一下：&lt;/p&gt;&lt;pre class="code"&gt;DataPager - GetQueryStringNavigateUrl
DataPagerField - SetDirty
DataPagerField - SetDataPager
LinqDataSourceView - ReleaseSelectContexts
ListViewDeletedEventArgs - SetKeys
ListViewDeletedEventArgs - SetValues
ListViewInsertedEventArgs - SetValues
ListViewUpdatedEventArgs - SetKeys
ListViewUpdatedEventArgs - SetNewValues
ListViewUpdatedEventArgs - SetOldValues
ConvertersCollection - CreateConverters
JavaScriptSerializer - ConverterExistsForType
JavaScriptSerializer - Serialize
UpdatePanelTrigger - SetOwner
ScriptDescriptor - RegisterDisposeForDescriptor
ScriptComponentDescriptor - RegisterDisposeForDescriptor
ScriptManager - AddFrameworkScripts
ScriptManager - AddScriptCollections
ScriptManager - CreateUniqueScriptKey
ScriptManager - GetScriptResourceUrl
ScriptManager - RegisterClientScriptBlockInternal
ScriptManager - RegisterClientScriptIncludeInternal
ScriptManager - RegisterStartupScriptInternal
ScriptManagerProxy - CollectScripts
ScriptManagerProxy - RegisterServices
ScriptReference - DetermineCulture
ScriptReference - GetAssembly
ScriptReference - GetPath
ScriptReference - GetResourceName
ScriptReference - ShouldUseDebugScript
ServiceReference - Register
UpdatePanel - ClearContent
UpdatePanel - SetAsyncPostBackMode
UpdatePanelTriggerCollection - HasTriggered
UpdatePanelTriggerCollection - Initialize&lt;/pre&gt;
&lt;p&gt;我从这些数据中得出的结论是：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;public类中的internal方法的确不多。&lt;/li&gt;
&lt;li&gt;使用internal来修饰属性是常见做法。&lt;/li&gt;
&lt;li&gt;一个方法是否属于某个类，由职责确定。&lt;/li&gt;
&lt;li&gt;如果某个方法不需要对外公开，则使用internal来修饰。&lt;/li&gt;&lt;/ul&gt;
&lt;p&gt;您的看法如何？&lt;/p&gt;</description>
      <comments>http://blog.zhaojie.me/2009/09/detect-internal-members-in-libraries.html#comments</comments>
      <pubDate>Sun, 13 Sep 2009 15:17:00 GMT</pubDate>
      <lastBuildDate>Sun, 13 Sep 2009 15:17:00 GMT</lastBuildDate>
    </item>
    <item>
      <author>jeffz@live.com (老赵)</author>
      <category domain="http://blog.zhaojie.me/practice/">实践优化</category>
      <category domain="http://blog.zhaojie.me/architecture/">架构设计</category>
      <title>与protected成员有关的单元测试方式</title>
      <link>http://blog.zhaojie.me/2009/08/unit-test-protected-method.html</link>
      <guid>http://blog.zhaojie.me/2009/08/unit-test-protected-method.html</guid>
      <description>&lt;p&gt;这是一篇简单的文章，讨论了单元测试中遇到protected成员的应对方案。此外，在文章最后也希望和大家讨论一下某个特殊的情况下的处理方法。&lt;/p&gt; &lt;p&gt;protected是一个有趣而有用的修饰符，它把方法的访问成员严格限制在自身或自己的子类身上。换句话说，在使用过程中，protected成员对外部是开放的（因为其他类可以通过继承来使用该成员），又是封闭的（不是自身或子类的一切成员都无法访问）。而对于单元测试来说，protected成员又是尴尬的，因为它的“开放”意味着我们必须对它进行单元测试，而“封闭”又阻碍了我们在单元测试中涉及protected成员。&lt;/p&gt; &lt;h1&gt;测试protected方法&lt;/h1&gt; &lt;p&gt;现在有一个类，其中包含一个protected方法：&lt;/p&gt;&lt;pre class="code"&gt;&lt;span style="color: blue"&gt;public class &lt;/span&gt;&lt;span style="color: #2b91af"&gt;SomeClass
&lt;/span&gt;{
    &lt;span style="color: blue"&gt;protected int &lt;/span&gt;SomeMethod(&lt;span style="color: blue"&gt;string &lt;/span&gt;arg) { ... }
}
&lt;/pre&gt;&lt;a href="http://11011.net/software/vspaste"&gt;&lt;/a&gt;
&lt;p&gt;如果我们需要对这个protected方法进行单元测试，可以在测试代码中准备一个辅助类型：&lt;/p&gt;&lt;pre class="code"&gt;&lt;span style="color: blue"&gt;public class &lt;/span&gt;&lt;span style="color: #2b91af"&gt;SomeClassForTest &lt;/span&gt;: &lt;span style="color: #2b91af"&gt;SomeClass
&lt;/span&gt;{
    &lt;span style="color: blue"&gt;public int &lt;/span&gt;PublicSomeMethod(&lt;span style="color: blue"&gt;string &lt;/span&gt;arg)
    {
        &lt;span style="color: blue"&gt;return this&lt;/span&gt;.SomeMethod(arg);
    }
}
&lt;/pre&gt;&lt;a href="http://11011.net/software/vspaste"&gt;&lt;/a&gt;
&lt;p&gt;于是在单元测试中，便可以通过调用PublicSomeMethod来测试基类的SomeMethod方法：&lt;/p&gt;&lt;pre class="code"&gt;&lt;span style="color: blue"&gt;var &lt;/span&gt;testClass = &lt;span style="color: blue"&gt;new &lt;/span&gt;&lt;span style="color: #2b91af"&gt;SomeClassForTest&lt;/span&gt;();
&lt;span style="color: blue"&gt;var &lt;/span&gt;result = testClass.PublicSomeMethod(&lt;span style="color: blue"&gt;null&lt;/span&gt;);
&lt;span style="color: #2b91af"&gt;Assert&lt;/span&gt;.Equal(0, result);
&lt;/pre&gt;&lt;a href="http://11011.net/software/vspaste"&gt;&lt;/a&gt;
&lt;p&gt;非常简单。&lt;/p&gt;
&lt;p&gt;如果您觉得麻烦，也可以将SomeClass类中的SomeMethod方法改为protected internal，这样便可以在InternalVisibleTo的测试程序集中使用了。不过，我觉得为单元测试而改变成员的访问级别不是一个合适的做法。&lt;/p&gt;
&lt;h1&gt;对protected方法进行Mock&lt;/h1&gt;
&lt;p&gt;现在有一个类，其中有一个protected方法：&lt;/p&gt;&lt;pre class="code"&gt;&lt;span style="color: blue"&gt;public class &lt;/span&gt;&lt;span style="color: #2b91af"&gt;SomeClass
&lt;/span&gt;{
    &lt;span style="color: blue"&gt;protected virtual int &lt;/span&gt;SomeMethod(&lt;span style="color: blue"&gt;string &lt;/span&gt;arg) { ... }
}
&lt;/pre&gt;&lt;a href="http://11011.net/software/vspaste"&gt;&lt;/a&gt;
&lt;p&gt;并且，某个被测试的方法接受SomeClass作为参数。虽然被测试的方法不会直接调用SomeMethod方法，但是SomeMethod的实现会影响到公开接口的表现形式。于是，我们需要对SomeMethod进行Mock或Stub。为此，我们同样需要准备一个辅助类型：&lt;/p&gt;&lt;pre class="code"&gt;&lt;span style="color: blue"&gt;public class &lt;/span&gt;&lt;span style="color: #2b91af"&gt;MockSomeClass &lt;/span&gt;: &lt;span style="color: #2b91af"&gt;SomeClass
&lt;/span&gt;{
    &lt;span style="color: blue"&gt;protected override int &lt;/span&gt;SomeMethod(&lt;span style="color: blue"&gt;string &lt;/span&gt;arg)
    {
        &lt;span style="color: blue"&gt;return this&lt;/span&gt;.PublicSomeMethod(arg);
    }

    &lt;span style="color: blue"&gt;public virtual int &lt;/span&gt;PublicSomeMethod(&lt;span style="color: blue"&gt;string &lt;/span&gt;arg)
    {
        &lt;span style="color: blue"&gt;return base&lt;/span&gt;.SomeMethod(arg);
    }
}
&lt;/pre&gt;&lt;a href="http://11011.net/software/vspaste"&gt;&lt;/a&gt;
&lt;p&gt;在MockSomeClass中，我们覆盖了基类的SomeMethod实现，使它调用了子类中公开的PublicSomeMethod方法，而PublicSomeMethod内部又调用了基类的SomeMethod方法。因此，如果您不去进行任何处理，那么MockSomeClass会保持SomeMethod的实现不变。而如果您需要对SomeMethod进行Mock或Stub的时候，便可以从PublicSomeMethod下手：&lt;/p&gt;&lt;pre class="code"&gt;&lt;span style="color: #2b91af"&gt;Mock&lt;/span&gt;&amp;lt;&lt;span style="color: #2b91af"&gt;MockSomeClass&lt;/span&gt;&amp;gt; mockSomeClass = &lt;span style="color: blue"&gt;new &lt;/span&gt;&lt;span style="color: #2b91af"&gt;Mock&lt;/span&gt;&amp;lt;&lt;span style="color: #2b91af"&gt;MockSomeClass&lt;/span&gt;&amp;gt;() { CallBase = &lt;span style="color: blue"&gt;true &lt;/span&gt;};
mockSomeClass.Setup(c =&amp;gt; c.PublicSomeMethod(&lt;span style="color: #a31515"&gt;"123"&lt;/span&gt;)).Returns(123);

DoSomeTest(mockSomeClass.Object); &lt;span style="color: green"&gt;// use the mock object
&lt;/span&gt;&lt;/pre&gt;&lt;a href="http://11011.net/software/vspaste"&gt;&lt;/a&gt;
&lt;p&gt;也很容易。&lt;/p&gt;
&lt;h1&gt;为了可测试性&lt;/h1&gt;
&lt;p&gt;值得注意的是，为了“可测试性”，第二部分中的protected方法必须是virtual的，因为我们需要在子类中进行override。同理，Mock框架能够辅助的方法也必须是virtual的，即使是一个public方法。那么，您觉得这是为了可测试性而做出的让步吗？或者换句话说，您觉得，一个不可以override的protected方法，但是会影响到其他公开接口的功能，这是不是一个合理的设计呢？如果这是一个合理的设计，又不想作出这样的让步……我们又该怎么做呢？&lt;/p&gt;
&lt;p&gt;关于这点，我有自己的想法，不过还是想先听一下您的意见。&lt;/p&gt;</description>
      <comments>http://blog.zhaojie.me/2009/08/unit-test-protected-method.html#comments</comments>
      <pubDate>Fri, 28 Aug 2009 09:33:00 GMT</pubDate>
      <lastBuildDate>Fri, 28 Aug 2009 09:33:00 GMT</lastBuildDate>
    </item>
    <item>
      <author>jeffz@live.com (老赵)</author>
      <category domain="http://blog.zhaojie.me/practice/">实践优化</category>
      <category domain="http://blog.zhaojie.me/architecture/">架构设计</category>
      <title>所有的成员都应该是virtual的吗？</title>
      <link>http://blog.zhaojie.me/2009/08/virtually-necessary-members.html</link>
      <guid>http://blog.zhaojie.me/2009/08/virtually-necessary-members.html</guid>
      <description>&lt;p&gt;这是一个由来已久的讨论，由于Java默认所有的方法都是可以被override的（除非手动写成final），因此从C#语言设计起初就有此番争论，甚至让&lt;a href="http://www.artima.com/intv/nonvirtual.html"&gt;Anders都出来解释了一下&lt;/a&gt;。最近又有人在讨论这方面话题了，虽然我的看法并没有超出这些人所涉及的范畴，但是我还是打算谈一下我的理解。退几步说，就当补充一些“实例”吧。&lt;/p&gt; &lt;p&gt;此次的话题是由&lt;font face="http://neverindoubtnet.blogspot.com/2009/08/do-not-make-every-method-virtual.html"&gt;&lt;a href="http://neverindoubtnet.blogspot.com/2009/08/art-of-unit-testing.html"&gt;Ward Bell引起的&lt;/a&gt;&lt;/font&gt;，他在review了&lt;a href="http://weblogs.asp.net/ROsherove/"&gt;Roy Osherove&lt;/a&gt;的新书《&lt;a href="http://www.artofunittesting.com/"&gt;The Art of Unit Testing&lt;/a&gt;》之后认为，他不同意Roy给出的建议“将所有的成员默认为virtual”，为此他还&lt;a href="http://neverindoubtnet.blogspot.com/2009/08/do-not-make-every-method-virtual.html"&gt;独立开篇解释了他的观点&lt;/a&gt;。这篇文章引起的讨论较为热烈，我也打算在详细总结一番。与Ward观点对应的是，著名的&lt;a href="http://codebetter.com/blogs/jeremy.miller/default.aspx"&gt;Jeremy Miller&lt;/a&gt;希望.NET中所有的成员默认就是virtual的，而“月写博客80篇”的&lt;a href="http://ayende.com/Blog/"&gt;Oren Eini&lt;/a&gt;甚至认为&lt;a href="http://ayende.com/Blog/archive/2009/08/17/virtually-everything.aspx"&gt;所有的成员都应该标为virtual&lt;/a&gt;。&lt;/p&gt; &lt;p&gt;继承一个类并override掉其中的成员，是面向对象编程中最常用的方式之一。这是一种扩展方式，而能够被override的方法便是“扩展点”。所以我认为，是否把成员标记为virtual，其实涉及到的概念便是“是否把它开放为一个扩展点”。Oren认为“所有成员都应该virtual”则意味着“任何成员都是可扩展点”，而对于“默认为virtual”的观点来说，则意味着“倾向于打开更多的扩展点”——其实除了Oren有些极端外，“倾向性”代表的更多是一种“口味”，因为无论是Java还是.NET，都可以标记一个成员能否被override。&lt;/p&gt; &lt;p&gt;我不想讨论“口味”问题，不过我的观点与Ward类似，即使在C#出现之前，我也一直不太喜欢Java的这个特性（不过当时相关体会比较少，所以感觉并不强烈）。Oren认为打开更多扩展点，有助于从各方面进行扩展，他说他的这个做法也过于也得到了较多的“实惠”。不过我认为，这是由于Oren的能力过于厉害，并且知道该做什么不该做什么，并且可以对自己作的事情所负责决定的。&lt;/p&gt; &lt;p&gt;对于一个可“全面扩展”的类型来说，意味着开发人员有更多的自由，进而意味着选择（即使是做同一件事情）。但是选择多，则同样意味着我们需要了解的多，一个不慎可能就会发现没有得到预期的效果。例如，在继承了ASP.NET的Control类之后，您要改变它输出的内容，您会选择覆盖哪一个方法？&lt;/p&gt;&lt;pre class="code"&gt;&lt;span style="color: blue"&gt;protected internal virtual void &lt;/span&gt;Render(&lt;span style="color: #2b91af"&gt;HtmlTextWriter &lt;/span&gt;writer)
{
    &lt;span style="color: blue"&gt;this&lt;/span&gt;.RenderChildren(writer);
}

&lt;span style="color: blue"&gt;protected internal virtual void &lt;/span&gt;RenderChildren(&lt;span style="color: #2b91af"&gt;HtmlTextWriter &lt;/span&gt;writer)
{
    ...
}
&lt;/pre&gt;
&lt;p&gt;您可能会说，覆盖哪个都可以。但是，它们其实都有不同的语义，只是因为在Control基类中Render自身就是Render所有的子控件。但是到了子类中，Render自身可能就会涉及到边框等其他内容了。如果您随便选一个，您的类型看上去没有问题，但是如果别人希望继承你写的类，补充一些实现，那么你的“选择”就会影响到他的结果了。当然我并不是说Control类设计的不对，它的设计我觉得是正确的，我只是想说明，如果每个成员都可扩展，那么用户在真正需要扩展的时候就会比较麻烦了。&lt;/p&gt;
&lt;p&gt;架构是一种扩展，也是一种约束，限制了别人可以怎么做，也必须怎么做。我们虽然无法避免别人的恶意行为，但是良好的扩展点也可以给别人更好的指导。&lt;/p&gt;
&lt;p&gt;再举一个例子。在.NET中，最容易扩展扩展的抽象元素是什么呢？应该是“接口”。接口中的所有成员都是由实现方提供的，除了成员的签名之外，接口并没有作任何限制。正如我在之前&lt;a href="http://blog.zhaojie.me/2009/08/more-on-class-and-interface.html"&gt;写过的一篇文章里提到&lt;/a&gt;，别人完全可以实现出外强中干的对象：&lt;/p&gt;&lt;pre class="code"&gt;&lt;span style="color: blue"&gt;public interface &lt;/span&gt;&lt;span style="color: #2b91af"&gt;IList&lt;/span&gt;&amp;lt;T&amp;gt;
{
    &lt;span style="color: blue"&gt;void &lt;/span&gt;Add(T item);
    &lt;span style="color: blue"&gt;int &lt;/span&gt;Count { &lt;span style="color: blue"&gt;get&lt;/span&gt;; }
 
    ...
}
&lt;/pre&gt;
&lt;p&gt;根据接口中隐含的协议，Add方法调用之后，Count必须加一。但是这个协议并无法加诸于实现之上。如果要提供这方面的约束，我们只能公开一部分的扩展点，而不是把所有的职责交给实现方：&lt;/p&gt;&lt;pre class="code"&gt;&lt;span style="color: blue"&gt;public abstract class &lt;/span&gt;&lt;span style="color: #2b91af"&gt;ListBase&lt;/span&gt;&amp;lt;T&amp;gt;
{ 
    &lt;span style="color: blue"&gt;public int &lt;/span&gt;Count { &lt;span style="color: blue"&gt;get&lt;/span&gt;; &lt;span style="color: blue"&gt;private set&lt;/span&gt;; }
 
    &lt;span style="color: blue"&gt;public void &lt;/span&gt;Add(T item)
    {
        &lt;span style="color: blue"&gt;this&lt;/span&gt;.Count++;
        &lt;span style="color: blue"&gt;this&lt;/span&gt;.AddCore(item);
    }
 
    &lt;span style="color: blue"&gt;protected abstract void &lt;/span&gt;AddCore(T item);
 
    ...
}
&lt;/pre&gt;
&lt;p&gt;Ward也举了一个“电梯”的例子。电梯有一个Up方法，调用它则意味着电梯上升。但是如果把Up这个关键行为扩展出去，那么别人在“修改电路”（即override这个Up方法）的时候，可能就会把电梯搞乱套了，例如原本应该先关门再启动，现在可能先启动再关门，甚至一旦Up电梯就下降了。Oren认为扩展方应该为自己的扩展负责，但是我还是认为，扩展点应该和成员访问级别等东西一样，只给出必要的，控制住关键的。&lt;/p&gt;
&lt;p&gt;最后的例子也是常见的：&lt;/p&gt;&lt;pre class="code"&gt;&lt;span style="color: blue"&gt;public class &lt;/span&gt;&lt;span style="color: #2b91af"&gt;SomeClass
&lt;/span&gt;{
    &lt;span style="color: blue"&gt;public void &lt;/span&gt;SomeMethed()
    {
        &lt;span style="color: blue"&gt;this&lt;/span&gt;.SomeMethed(&lt;span style="color: #2b91af"&gt;String&lt;/span&gt;.Empty);
    }

    &lt;span style="color: blue"&gt;public void &lt;/span&gt;SomeMethed(&lt;span style="color: blue"&gt;string &lt;/span&gt;s)
    {
        &lt;span style="color: blue"&gt;this&lt;/span&gt;.SomeMethod(s, 0);
    }

    &lt;span style="color: blue"&gt;public&lt;/span&gt; &lt;span style="color: red"&gt;virtual&lt;/span&gt; &lt;span style="color: blue"&gt;void &lt;/span&gt;SomeMethod(&lt;span style="color: blue"&gt;string &lt;/span&gt;s, &lt;span style="color: blue"&gt;int &lt;/span&gt;i)
    {
        &lt;span style="color: green"&gt;// ...
    &lt;/span&gt;}
}
&lt;/pre&gt;
&lt;p&gt;为了方便起见，我们常常会对类型中的方法给出重载，其中大部分的重载最终都委托给一个唯一的核心方法。当用户继承SomeClass类之后，他便拥有了一个唯一的扩展点，这样便可以确保这个类的行为按照一定的“准则”在正常开展。否则的话，用户就需要在三个方法中进行选择性的override，并且要平衡三者的行为。因为在扩展SomeClass的时候，并不知道SomeClass的使用者会调用SomeMethod的哪个重载。&lt;/p&gt;
&lt;p&gt;这对于单元测试一样。如果三个方法都可以Mock，那么在测试时我们可能就会去“猜测”用户究竟调用了哪个SomeMethod重载，而这是不确定的，也是容易变化的。如果我们只有一个重载可以Mock，那么则意味着“别挑了，就是这个”。所以，我有时候也不太喜欢&lt;a href="http://www.typemock.com/"&gt;Type Mock&lt;/a&gt;如此强大有力的Mock框架，因为它可能会破坏了被测试方的设计，把一切都变成了扩展点——虽然这对于测试来说的确很方便，几乎不输给动态语言了。&lt;/p&gt;
&lt;p&gt;“可测试性”也是设计出来，不是语言或平台自动赋予的。这就是“&lt;a href="http://en.wikipedia.org/wiki/Design_For_Test"&gt;design for testability&lt;/a&gt;”的体现之一吧。&lt;/p&gt;
&lt;p&gt;您的观点呢？&lt;/p&gt;</description>
      <comments>http://blog.zhaojie.me/2009/08/virtually-necessary-members.html#comments</comments>
      <pubDate>Fri, 28 Aug 2009 02:56:00 GMT</pubDate>
      <lastBuildDate>Fri, 28 Aug 2009 02:56:00 GMT</lastBuildDate>
    </item>
    <item>
      <author>jeffz@live.com (老赵)</author>
      <category domain="http://blog.zhaojie.me/practice/">实践优化</category>
      <category domain="http://blog.zhaojie.me/architecture/">架构设计</category>
      <title>再谈public类型中internal成员的坏味道</title>
      <link>http://blog.zhaojie.me/2009/08/more-on-bad-smell-of-internal-members-in-public-classes.html</link>
      <guid>http://blog.zhaojie.me/2009/08/more-on-bad-smell-of-internal-members-in-public-classes.html</guid>
      <description>&lt;p&gt;&lt;a href="http://blog.zhaojie.me/2009/08/internal-member-is-bad-smell.html"&gt;上一篇文章&lt;/a&gt;里我讨论了一个类中internal成员可能会造成的坏味道，并且认为如果您的类型中出现了这个情况，可能就值得检查一下设计上是不是有问题了。文章中我提出了三种可能出现internal的情况，其中两种争议不大，不过对于“public类中是否应该出现internal成员”这一点似乎引起了一些争议。从评论中发现，讨论的一部分焦点并不是我的本意，这可能是我前文描述地较为简单而造成的，因此我现在对于这个方面再进行略为详细的探讨。&lt;/p&gt; &lt;p&gt;首先可能还是需要强调的是，我并没有说不该用internal关键字，有些朋友提出，internal关键字可以控制成员的访问级别，可以把一些非标准的类型（如unsigned int）控制在内部。这些都对，但它们不是我谈论的目标。我讨论的不是internal关键字是否有用（这不值得讨论，怎么可能没用），而是“在类中的internal成员”是否为一种合适的设计。这涉及类的职责，语义，类之间的协作等话题，并不是在讨论简单的“访问级别”控制。&lt;/p&gt; &lt;p&gt;在前文中，我用简单的代码片断来说明“public类中的internal成员可能是一个坏味道”，这次我打算使用更详细的代码来说明问题。请看这样的类型：&lt;/p&gt;&lt;pre class="code"&gt;&lt;span style="color: blue"&gt;internal class &lt;/span&gt;&lt;span style="color: #2b91af"&gt;ProductDetail &lt;/span&gt;{ }

&lt;span style="color: blue"&gt;public class &lt;/span&gt;&lt;span style="color: #2b91af"&gt;Product
&lt;/span&gt;{
    &lt;span style="color: blue"&gt;public int &lt;/span&gt;ProductID { &lt;span style="color: blue"&gt;get&lt;/span&gt;; &lt;span style="color: blue"&gt;set&lt;/span&gt;; }

    &lt;span style="color: blue"&gt;public string &lt;/span&gt;Name { &lt;span style="color: blue"&gt;get&lt;/span&gt;; &lt;span style="color: blue"&gt;set&lt;/span&gt;; }

    &lt;span style="color: red"&gt;internal &lt;/span&gt;&lt;span style="color: #2b91af"&gt;XElement &lt;/span&gt;GetXmlData()
    {
        &lt;span style="color: blue"&gt;return new &lt;/span&gt;&lt;span style="color: #2b91af"&gt;XElement&lt;/span&gt;(&lt;span style="color: #a31515"&gt;"Product"&lt;/span&gt;,
            &lt;span style="color: blue"&gt;new &lt;/span&gt;&lt;span style="color: #2b91af"&gt;XElement&lt;/span&gt;(&lt;span style="color: #a31515"&gt;"ProductID"&lt;/span&gt;, &lt;span style="color: blue"&gt;this&lt;/span&gt;.ProductID),
            &lt;span style="color: blue"&gt;new &lt;/span&gt;&lt;span style="color: #2b91af"&gt;XElement&lt;/span&gt;(&lt;span style="color: #a31515"&gt;"Name"&lt;/span&gt;, &lt;span style="color: blue"&gt;this&lt;/span&gt;.Name),
            &lt;span style="color: blue"&gt;new &lt;/span&gt;&lt;span style="color: #2b91af"&gt;XElement&lt;/span&gt;(&lt;span style="color: #a31515"&gt;"Detail"&lt;/span&gt;, ...)); &lt;span style="color: green"&gt;// internal detail
    &lt;/span&gt;}

    &lt;span style="color: blue"&gt;private &lt;/span&gt;&lt;span style="color: #2b91af"&gt;ProductDetail &lt;/span&gt;m_internalDetail;
}
&lt;/pre&gt;
&lt;p&gt;您的项目中有一个Product类，其中有一些公开的成员，对外释放了Product对象的ID，Name以及一些公开的行为。不过在项目“内部”还有一个需求，是将一个Product转化为XML进行保存或传输。这个功能只对内部有作用，因此Product类中还有一个internal方法称为是GetXmlData，返回一个表示自身的XElement对象。其中会包含它的一些公开信息，以及只有Product类型“自己”才知道的私有信息，这里我们把它称为是ProductDetail。&lt;/p&gt;
&lt;p&gt;现在，我们可以这样调用GetXmlData方法：&lt;/p&gt;&lt;pre class="code"&gt;&lt;span style="color: #2b91af"&gt;Product &lt;/span&gt;product = &lt;span style="color: blue"&gt;new &lt;/span&gt;&lt;span style="color: #2b91af"&gt;Product&lt;/span&gt;();
&lt;span style="color: #2b91af"&gt;XElement &lt;/span&gt;xml = product.GetXmlData();
&lt;/pre&gt;&lt;a href="http://11011.net/software/vspaste"&gt;&lt;/a&gt;
&lt;p&gt;现在，GetXmlData方式是internal的，因为它只对项目内部有作用，这也是internal关键字的作用，控制访问级别嘛。似乎这个设计没有什么问题，但是请思考一下，我之前为什么说公开类的internal成员可能是一个坏味道呢？&lt;/p&gt;
&lt;p&gt;其实就是在“职责”上。因为这个对象既有public成员，又有internal成员，这意味着它有一部分功能是分开的，一部分功能是对内的，这在某些时候就&lt;span style="color:red;"&gt;可能&lt;/span&gt;会意味着这个对象承担了两种“职责”。就如Product对象，将自己的信息生成为XML是Product对象的职责吗？在您的环境中答案可能为“是”，不过在这里就认为不太妥当吧。Product对象知道自己有哪些信息，但是它按理来说，不应该负责XML的生成，不应该负责XML的格式、元素名、命名空间等XML特有的属性。有关XML生成的逻辑应该不属于Product类，这应该是其他类型的职责。&lt;/p&gt;
&lt;p&gt;于是，我们对上面的代码进行重构：&lt;/p&gt;&lt;pre class="code"&gt;&lt;span style="color: blue"&gt;internal class &lt;/span&gt;&lt;span style="color: #2b91af"&gt;ProductDetail &lt;/span&gt;{ }

&lt;span style="color: blue"&gt;public class &lt;/span&gt;&lt;span style="color: #2b91af"&gt;Product
&lt;/span&gt;{
    &lt;span style="color: blue"&gt;public int &lt;/span&gt;ProductID { &lt;span style="color: blue"&gt;get&lt;/span&gt;; &lt;span style="color: blue"&gt;set&lt;/span&gt;; }

    &lt;span style="color: blue"&gt;public string &lt;/span&gt;Name { &lt;span style="color: blue"&gt;get&lt;/span&gt;; &lt;span style="color: blue"&gt;set&lt;/span&gt;; }

    &lt;span style="color: blue"&gt;private &lt;/span&gt;&lt;span style="color: #2b91af"&gt;ProductDetail &lt;/span&gt;m_internalDetail;

    &lt;span style="color: red"&gt;internal &lt;/span&gt;&lt;span style="color: #2b91af"&gt;ProductDetail &lt;/span&gt;Detail
    {
        &lt;span style="color: blue"&gt;get
        &lt;/span&gt;{
            &lt;span style="color: blue"&gt;return this&lt;/span&gt;.m_internalDetail;
        }
    }
}

&lt;span style="color: blue"&gt;internal class &lt;/span&gt;&lt;span style="color: #2b91af"&gt;ProductXmlGenerator
&lt;/span&gt;{
    &lt;span style="color: blue"&gt;public &lt;/span&gt;&lt;span style="color: #2b91af"&gt;XElement &lt;/span&gt;GetXmlData(&lt;span style="color: #2b91af"&gt;Product &lt;/span&gt;product)
    {
        &lt;span style="color: blue"&gt;return new &lt;/span&gt;&lt;span style="color: #2b91af"&gt;XElement&lt;/span&gt;(&lt;span style="color: #a31515"&gt;"Product"&lt;/span&gt;,
            &lt;span style="color: blue"&gt;new &lt;/span&gt;&lt;span style="color: #2b91af"&gt;XElement&lt;/span&gt;(&lt;span style="color: #a31515"&gt;"ProductID"&lt;/span&gt;, product.ProductID),
            &lt;span style="color: blue"&gt;new &lt;/span&gt;&lt;span style="color: #2b91af"&gt;XElement&lt;/span&gt;(&lt;span style="color: #a31515"&gt;"Name"&lt;/span&gt;, product.Name),
            &lt;span style="color: blue"&gt;new &lt;/span&gt;&lt;span style="color: #2b91af"&gt;XElement&lt;/span&gt;(&lt;span style="color: #a31515"&gt;"Detail"&lt;/span&gt;, ...)); &lt;span style="color: green"&gt;// internal detail
    &lt;/span&gt;}
}
&lt;/pre&gt;&lt;a href="http://11011.net/software/vspaste"&gt;&lt;/a&gt;
&lt;p&gt;现在使用的代码便修改为：&lt;/p&gt;&lt;pre class="code"&gt;&lt;span style="color: #2b91af"&gt;Product &lt;/span&gt;product = &lt;span style="color: blue"&gt;new &lt;/span&gt;&lt;span style="color: #2b91af"&gt;Product&lt;/span&gt;();
&lt;span style="color: #2b91af"&gt;ProductXmlGenerator &lt;/span&gt;xmlGenderator = &lt;span style="color: blue"&gt;new &lt;/span&gt;&lt;span style="color: #2b91af"&gt;ProductXmlGenerator&lt;/span&gt;();
&lt;span style="color: #2b91af"&gt;XElement &lt;/span&gt;xml = xmlGenderator.GetXmlData(product);
&lt;/pre&gt;&lt;a href="http://11011.net/software/vspaste"&gt;&lt;/a&gt;
&lt;p&gt;至此，XML生成所需要的逻辑便转移到ProductXmlGenerator类中，需要获得XML数据的时候，便实例化一个ProductXmlGenerator，将一个Product对象转化为XML。不过，由于此时Product对象的数据需要被其他类访问到了，我们又必须创建一个internal的Detail属性，将原本私有的ProductDetail字段暴露给Product之外的对象。&lt;/p&gt;
&lt;p&gt;不过，目前的做法还是有一些问题。虽然生成XML的逻辑被分离的出去了，但是另一部分原本应该属于Product的职责也被转移了。一般来说，只有Product自己才知道“有哪些数据需要被保存”，它不知道的只是“应该如何保存这些数据”，而后者才是我们需要分离出去的逻辑。但是ProductXmlGenerator同样包含了本不该属于自己的职责，它也去关心Product对象的细节了。这也是为什么我们需要把原本是私密的ProductDetail也通过internal的方式释放出去。&lt;/p&gt;
&lt;p&gt;其实在面向对象设计领域，这也是一个有名的“准则”，那就是“&lt;a href="http://www.pragprog.com/articles/tell-dont-ask"&gt;Tell, don’t ask&lt;/a&gt;”。现在的做法便破坏了这个准则。&lt;/p&gt;
&lt;p&gt;因此，再次重构：&lt;/p&gt;&lt;pre class="code"&gt;&lt;span style="color: blue"&gt;internal interface &lt;/span&gt;&lt;span style="color: #2b91af"&gt;IDataCollector
&lt;/span&gt;{
    &lt;span style="color: blue"&gt;void &lt;/span&gt;CollectInt32(&lt;span style="color: blue"&gt;string &lt;/span&gt;name, &lt;span style="color: blue"&gt;int &lt;/span&gt;value);
    &lt;span style="color: blue"&gt;void &lt;/span&gt;CollectString(&lt;span style="color: blue"&gt;string &lt;/span&gt;name, &lt;span style="color: blue"&gt;string &lt;/span&gt;value);
}

&lt;span style="color: blue"&gt;internal interface &lt;/span&gt;&lt;span style="color: #2b91af"&gt;IDataCollectable
&lt;/span&gt;{
    &lt;span style="color: blue"&gt;void &lt;/span&gt;Collect(&lt;span style="color: #2b91af"&gt;IDataCollector &lt;/span&gt;collector);
}

&lt;span style="color: blue"&gt;internal class &lt;/span&gt;&lt;span style="color: #2b91af"&gt;XmlDataCollector &lt;/span&gt;: &lt;span style="color: #2b91af"&gt;IDataCollector
&lt;/span&gt;{
    &lt;span style="color: blue"&gt;void &lt;/span&gt;&lt;span style="color: #2b91af"&gt;IDataCollector&lt;/span&gt;.CollectInt32(&lt;span style="color: blue"&gt;string &lt;/span&gt;name, &lt;span style="color: blue"&gt;int &lt;/span&gt;value) { }
    &lt;span style="color: blue"&gt;void &lt;/span&gt;&lt;span style="color: #2b91af"&gt;IDataCollector&lt;/span&gt;.CollectString(&lt;span style="color: blue"&gt;string &lt;/span&gt;name, &lt;span style="color: blue"&gt;string &lt;/span&gt;value) { }

    &lt;span style="color: blue"&gt;public &lt;/span&gt;&lt;span style="color: #2b91af"&gt;XElement &lt;/span&gt;Result { &lt;span style="color: blue"&gt;get &lt;/span&gt;{ … } }
}

&lt;span style="color: blue"&gt;internal class &lt;/span&gt;&lt;span style="color: #2b91af"&gt;ProductDetail &lt;/span&gt;{ }

&lt;span style="color: blue"&gt;public class &lt;/span&gt;&lt;span style="color: #2b91af"&gt;Product &lt;/span&gt;: &lt;span style="color: #2b91af"&gt;IDataCollectable
&lt;/span&gt;{
    &lt;span style="color: blue"&gt;public int &lt;/span&gt;ProductID { &lt;span style="color: blue"&gt;get&lt;/span&gt;; &lt;span style="color: blue"&gt;set&lt;/span&gt;; }

    &lt;span style="color: blue"&gt;public string &lt;/span&gt;Name { &lt;span style="color: blue"&gt;get&lt;/span&gt;; &lt;span style="color: blue"&gt;set&lt;/span&gt;; }

    &lt;span style="color: blue"&gt;private &lt;/span&gt;&lt;span style="color: #2b91af"&gt;ProductDetail &lt;/span&gt;m_internalDetail;

    &lt;span style="color: blue"&gt;void &lt;/span&gt;&lt;span style="color: #2b91af"&gt;IDataCollectable&lt;/span&gt;.Collect(&lt;span style="color: #2b91af"&gt;IDataCollector &lt;/span&gt;collector)
    {
        collector.CollectInt32(&lt;span style="color: #a31515"&gt;"ProductID"&lt;/span&gt;, &lt;span style="color: blue"&gt;this&lt;/span&gt;.ProductID);
        collector.CollectString(&lt;span style="color: #a31515"&gt;"Name"&lt;/span&gt;, &lt;span style="color: blue"&gt;this&lt;/span&gt;.Name);
        &lt;span style="color: green"&gt;// collect the details
    &lt;/span&gt;}
}
&lt;/pre&gt;&lt;a href="http://11011.net/software/vspaste"&gt;&lt;/a&gt;
&lt;p&gt;使用方式如下：&lt;/p&gt;&lt;pre class="code"&gt;&lt;span style="color: #2b91af"&gt;IDataCollectable &lt;/span&gt;product = &lt;span style="color: blue"&gt;new &lt;/span&gt;&lt;span style="color: #2b91af"&gt;Product&lt;/span&gt;();
&lt;span style="color: #2b91af"&gt;XmlDataCollector &lt;/span&gt;collector = &lt;span style="color: blue"&gt;new &lt;/span&gt;&lt;span style="color: #2b91af"&gt;XmlDataCollector&lt;/span&gt;();
product.Collect(collector);
&lt;span style="color: #2b91af"&gt;XElement &lt;/span&gt;element = collector.Result;
&lt;/pre&gt;&lt;a href="http://11011.net/software/vspaste"&gt;&lt;/a&gt;
&lt;p&gt;至此，我们提取出ICollectable和ICollector两个接口，让Product关心自己有哪些数据应该被“收集”，而XmlDataCollector则负责将收集的数据转化为合适的XML。大家完全通过抽象进行交互，各司其职。如果需要的话，系统中也可以出现多种收集器（如JsonDataCollector，BinaryDataCollector），同样可以出现多种可收集的对象。&lt;/p&gt;
&lt;p&gt;您可能注意到了，无论是Product还是XmlDataCollector都是显示实现internal接口的。这便是前文评论中&lt;a href="http://www.cnblogs.com/Ivony/"&gt;Ivony...&lt;/a&gt;同学所说的“被很多人忽视”的做法。也是因为如此，我们的代码中使用IDataCollectable对象来引用product对象。如果您想直接在Product对象上调用Collect方法，那就必须加上一个internal的Collect方法了&lt;/p&gt;
&lt;p&gt;嗯？这不是又出现internal成员了吗？没错，不过我从来没有像说明“internal成员是一定不能使用的”，我强调的只是一种“倾向性”，一种“职责不明”的倾向性。如果你确定这个internal成员的职责没有任何问题，而且肯定是必要的，那就这样使用吧。我们不是为了去除internal而去除internal，否则和为了设计而设计，为了敏捷而敏捷有什么区别呢？&lt;/p&gt;
&lt;p&gt;哦，对了，最后一提，其实我们最终的做法，和.NET框架中ISerializable还是颇为相像的。&lt;/p&gt;</description>
      <comments>http://blog.zhaojie.me/2009/08/more-on-bad-smell-of-internal-members-in-public-classes.html#comments</comments>
      <pubDate>Thu, 27 Aug 2009 02:49:00 GMT</pubDate>
      <lastBuildDate>Thu, 27 Aug 2009 02:49:00 GMT</lastBuildDate>
    </item>
    <item>
      <author>jeffz@live.com (老赵)</author>
      <category domain="http://blog.zhaojie.me/practice/">实践优化</category>
      <category domain="http://blog.zhaojie.me/architecture/">架构设计</category>
      <title>类中的internal成员可能是一种坏味道</title>
      <link>http://blog.zhaojie.me/2009/08/internal-member-is-bad-smell.html</link>
      <guid>http://blog.zhaojie.me/2009/08/internal-member-is-bad-smell.html</guid>
      <description>&lt;h1&gt;前言&lt;/h1&gt; &lt;p&gt;最近除了搞ASP.NET MVC之外，我也在思考一些编程实践方面的问题。昨天在回家路上，我忽然对一个问题产生了较为清晰的认识。或者说，原先只是有一丝细微的感觉，而现在将它和一些其他的方面进行了联系，也显得颇为“完备”。这就是问题便是：如何对待类中internal成员。我现在认为“类中的internal成员可能是一个坏味道”，换句话说，&lt;font color="#ff0000"&gt;如果您的类中出现了internal的成员，就可能是设计上的问题了&lt;/font&gt;。&lt;/p&gt; &lt;p&gt;可能这个命题说得还有些笼统，所以再详细地描述一下比较妥当。我的意思是，您的类库中出现internal的类型是完全没有问题的（也肯定是无法避免的）。然而，一个经过良好设计的类型，是应该很少出现internal的方法或属性的（字段就不在考虑范围，因为它应该永远是私有的）。其中有例外，如“构造函数”的修饰级别，稍后会再谈到。&lt;/p&gt; &lt;p&gt;C#中一个类中的成员有四种修饰级别：&lt;/p&gt; &lt;ul&gt; &lt;li&gt;public：完全开放，谁都能访问。  &lt;li&gt;private：完全封闭，只有类自身可以访问。  &lt;li&gt;internal：只对相同程序集，或使用&lt;a href="http://msdn.microsoft.com/en-us/library/system.runtime.compilerservices.internalsvisibletoattribute.aspx"&gt;InternalVisibleToAttribute&lt;/a&gt;标记的程序集开放。  &lt;li&gt;protected：只对子类开放。&lt;/li&gt;&lt;/ul&gt; &lt;p&gt;您也可以将protected和internal修饰同一个成员，这使得类中的一个成员可以拥有5种不同的访问权限。我认为，其中pubic、private和protected级别的含义是清晰而纯粹的，而internal的开放程度则是像是一个“灰色地带”。&lt;/p&gt; &lt;h1&gt;Internal类中的Internal成员&lt;/h1&gt; &lt;p&gt;我们为什么会使用internal修饰符？最简单的答案，自然是为了让相同程序集内类型可以访问，但是不对外部开放。那么我们什么时候会用这种访问级别呢？可能是这样的：&lt;/p&gt;&lt;pre class="code"&gt;&lt;span style="color: red"&gt;internal&lt;/span&gt; &lt;span style="color: blue"&gt;class &lt;/span&gt;&lt;span style="color: #2b91af"&gt;SomeClass
&lt;/span&gt;{
    &lt;span style="color: blue"&gt;internal void &lt;/span&gt;SomeMethod() { }
}
&lt;/pre&gt;
&lt;p&gt;请注意，这里我们在一个internal的类型中使用了internal来修饰这个方法。这是一种累赘，因为它和public修饰效果完全一致，这会造成不清晰的修饰性（灰色地带）。因此，在internal类型中，所有的成员只能是public、private和protected访问级别。也就是说，上面的代码应该改成：&lt;/p&gt;&lt;pre class="code"&gt;&lt;span style="color: blue"&gt;internal class &lt;/span&gt;&lt;span style="color: #2b91af"&gt;SomeClass
&lt;/span&gt;{
    &lt;span style="color: red"&gt;public&lt;/span&gt; &lt;span style="color: blue"&gt;void &lt;/span&gt;SomeMethod() { }
}
&lt;/pre&gt;
&lt;p&gt;于是，内部类中哪些是私有的，哪些是公开的（可以被相同程序集内访问到）一目了然。这个类的职责也非常明确。&lt;/p&gt;
&lt;h1&gt;Public类的Internal成员&lt;/h1&gt;
&lt;p&gt;这个问题就麻烦了许多，因为此时类中的internal成员含义就非常明确了：&lt;/p&gt;&lt;pre class="code"&gt;&lt;span style="color: blue"&gt;public class &lt;/span&gt;&lt;span style="color: #2b91af"&gt;SomeClass
&lt;/span&gt;{
    &lt;span style="color: blue"&gt;&lt;span style="color: red"&gt;internal&lt;/span&gt; void &lt;/span&gt;SomeMethod() { }
}
&lt;/pre&gt;
&lt;p&gt;public类中的internal成员可以被相同程序集内的类型访问到，而对外部的程序集是隐藏的。这意味着，这个类的功能分了两部分，一部分对所有人公开，还有一部分对自己人公开，对其他人关闭。在很多时候，这可能意味着一个类拥有了两种职责，一种对外，一种对内，而这种情况显然违背了“&lt;a href="http://en.wikipedia.org/wiki/Single_responsibility_principle"&gt;单一职责原则&lt;/a&gt;”。这时候我们可能需要重构，把一部分对内的职责封装为额外的internal类型，并负责内部逻辑的交互。如此，代码可能就会写成这样：&lt;/p&gt;&lt;pre class="code"&gt;&lt;span style="color: blue"&gt;internal class &lt;/span&gt;&lt;span style="color: #2b91af"&gt;InternalClass
&lt;/span&gt;{
    &lt;span style="color: blue"&gt;private &lt;/span&gt;&lt;span style="color: #2b91af"&gt;SomeClass&lt;/span&gt; m_someClass;

    &lt;span style="color: blue"&gt;public &lt;/span&gt;InternalClass(&lt;span style="color: #2b91af"&gt;SomeClass&lt;/span&gt; someClass)
    {
        &lt;span style="color: blue;"&gt;this&lt;/span&gt;.m_someClass = someClass;
    }

    &lt;span style="color: blue"&gt;public void &lt;/span&gt;SomeMethod()
    {
        &lt;span style="color: green;"&gt;/* use data on this.m_someClass. */&lt;/span&gt;
    }
}

&lt;span style="color: blue"&gt;public class &lt;/span&gt;&lt;span style="color: #2b91af"&gt;SomeClass
&lt;/span&gt;{
    &lt;span style="color: green"&gt;// public members
&lt;/span&gt;}
&lt;/pre&gt;
&lt;p&gt;不过这可能也是最容易产生争议的地方，因为这“削减”了internal的相当一大部分作用，此外还会造成代码的增加。而事实上，很多时候也应该在public类中使用internal方法，只要不违背“单一职责原则”即可。不过我想，这方面的“权衡”应该也是较为容易的，因为基本上所有的考量都是基于“职责”的。&lt;/p&gt;
&lt;p&gt;这也是我思考中经常遇到的问题，就是某种“实践”是不是属于“过度设计”了。我们的目标是快速发布，确保质量，而不是为了遵循原则而去遵循原则。在今后此类文章中，我也会提出类似的“权衡”，如果您有看法，欢迎和我交流。&lt;/p&gt;
&lt;h1&gt;为了单元测试而使用Internal成员&lt;/h1&gt;
&lt;p&gt;例如，一个类中有一个复杂的私有方法，我们希望对它进行单元测试。由于private成员无法被外部访问，因此我们会将其写成internal的方法：&lt;/p&gt;&lt;pre class="code"&gt;&lt;span style="color: blue"&gt;public class &lt;/span&gt;&lt;span style="color: #2b91af"&gt;SomeClass
&lt;/span&gt;{
    &lt;span style="color: blue"&gt;public void &lt;/span&gt;SomeMethod()
    { 
        &lt;span style="color: green"&gt;// do something...
        &lt;/span&gt;&lt;span style="color: blue"&gt;this&lt;/span&gt;.ComplexMethod();
        &lt;span style="color: green"&gt;// do something else...
    &lt;/span&gt;}

    &lt;span style="color: blue"&gt;internal void &lt;/span&gt;ComplexMethod() { }
}
&lt;/pre&gt;
&lt;p&gt;由于是internal方法，我们可以使用InternalVisibleToAttribute释放给其他程序集，就可以在那个程序集中编写单元测试代码。但是我认为这个做法不好。&lt;/p&gt;
&lt;p&gt;首先，我一直不喜欢为了“单元测试”而改变原有的封装性，即使改成internal成员后，对其他外部程序集来说并没有什么影响。 在MSDN Web Cast或其他一些地方，我可能讲过我们“可以”把private方法改为internal，仅仅是为单元测试。还有便是把protected也改成protected internal——我也会写文章讨论这个问题。&lt;/p&gt;
&lt;p&gt;其实这又涉及到是否应该测试私有方法的问题，我最近会再对此进行较为详细的讨论。如果您有一个需要测试的复杂的私有方法，这意味着这个私有方法可能会有独立的职责，独立的算法。我们又值得将其独立提取出来：&lt;/p&gt;&lt;pre class="code"&gt;&lt;span style="color: blue"&gt;internal class &lt;/span&gt;&lt;span style="color: #2b91af"&gt;ComplexClass
&lt;/span&gt;{
    &lt;span style="color: blue"&gt;public void &lt;/span&gt;ComplexMethod() { }
}

&lt;span style="color: blue"&gt;public class &lt;/span&gt;&lt;span style="color: #2b91af"&gt;SomeClass
&lt;/span&gt;{
    &lt;span style="color: blue"&gt;private &lt;/span&gt;&lt;span style="color: #2b91af"&gt;ComplexClass &lt;/span&gt;m_complexClass = &lt;span style="color: blue"&gt;new &lt;/span&gt;&lt;span style="color: #2b91af"&gt;ComplexClass&lt;/span&gt;();

    &lt;span style="color: blue"&gt;public void &lt;/span&gt;SomeMethod()
    { 
        &lt;span style="color: green"&gt;// do something...
        &lt;/span&gt;&lt;span style="color: blue"&gt;this&lt;/span&gt;.m_complexClass.ComplexMethod();
        &lt;span style="color: green"&gt;// do something else...
    &lt;/span&gt;}
}
&lt;/pre&gt;
&lt;p&gt;由于ComplexClass是internal的，我们便可以为其进行独立的单元测试。&lt;/p&gt;
&lt;h1&gt;一些例外情况&lt;/h1&gt;
&lt;p&gt;万事都有例外。例如对于构造函数来说，internal在很多时候是一个“必须”的修饰符：&lt;/p&gt;&lt;pre class="code"&gt;&lt;span style="color: blue"&gt;internal class &lt;/span&gt;&lt;span style="color: #2b91af"&gt;ComplexClass
&lt;/span&gt;{
    &lt;span style="color: blue"&gt;public virtual void &lt;/span&gt;ComplexMethod() { }
}

&lt;span style="color: blue"&gt;public class &lt;/span&gt;&lt;span style="color: #2b91af"&gt;SomeClass
&lt;/span&gt;{
    &lt;span style="color: blue"&gt;private &lt;/span&gt;&lt;span style="color: #2b91af"&gt;ComplexClass &lt;/span&gt;m_complexClass;

    &lt;span style="color: blue"&gt;public &lt;/span&gt;SomeClass()
        : &lt;span style="color: blue"&gt;this&lt;/span&gt;(&lt;span style="color: blue"&gt;new &lt;/span&gt;&lt;span style="color: #2b91af"&gt;ComplexClass&lt;/span&gt;())
    { }

    &lt;span style="color: blue"&gt;&lt;span style="color: red"&gt;internal&lt;/span&gt; &lt;/span&gt;SomeClass(&lt;span style="color: #2b91af"&gt;ComplexClass &lt;/span&gt;complexClass)
    {
        &lt;span style="color: blue"&gt;this&lt;/span&gt;.m_complexClass = complexClass;
    }

    &lt;span style="color: blue"&gt;public void &lt;/span&gt;SomeMethod()
    { 
        &lt;span style="color: green"&gt;// do something...
        &lt;/span&gt;&lt;span style="color: blue"&gt;this&lt;/span&gt;.m_complexClass.ComplexMethod();
        &lt;span style="color: green"&gt;// do something else...
    &lt;/span&gt;}
}
&lt;/pre&gt;
&lt;p&gt;由于其中一个构造函数是internal的，并接受一个对象，因此单元测试便可以利用这个构造函数“注入”一个对象（往往是一个Mock对象）。而对外公开的构造函数，便可以直接提供一个具体的实例，作为真实场景中的使用方式。&lt;/p&gt;
&lt;h1&gt;讨论&lt;/h1&gt;
&lt;p&gt;这便是我的观点：“类中的internal成员可能是一种坏味道”。您同意吗？如果您有什么看法，希望能够和我讨论一下。&lt;/p&gt;</description>
      <comments>http://blog.zhaojie.me/2009/08/internal-member-is-bad-smell.html#comments</comments>
      <pubDate>Wed, 26 Aug 2009 08:54:00 GMT</pubDate>
      <lastBuildDate>Wed, 26 Aug 2009 08:54:00 GMT</lastBuildDate>
    </item>
    <item>
      <author>jeffz@live.com (老赵)</author>
      <category domain="http://blog.zhaojie.me/architecture/">架构设计</category>
      <category domain="http://blog.zhaojie.me/translation/">翻译引进</category>
      <category domain="http://blog.zhaojie.me/practice/">实践优化</category>
      <title>Scaling a Rails Application from the Bottom Up</title>
      <link>http://blog.zhaojie.me/2007/06/793756.html</link>
      <guid>http://blog.zhaojie.me/2007/06/793756.html</guid>
      <description>&lt;p&gt;&lt;a title="http://media.joyent.com/JHoffmanRailsConf-May2007.pdf" href="http://media.joyent.com/JHoffmanRailsConf-May2007.pdf"&gt;http://media.joyent.com/JHoffmanRailsConf-May2007.pdf&lt;/a&gt;&lt;/p&gt; &lt;p&gt;这是今年Rails大会上的一个报告。虽然讲的是RoR应用程序，但实际该报告里包括了许多其他方面要素的阐述，例如操作系统，硬件配置等等。对于要构造大型网络应用的人来说是一个不可不读的文档。&lt;/p&gt;</description>
      <comments>http://blog.zhaojie.me/2007/06/793756.html#comments</comments>
      <pubDate>Fri, 22 Jun 2007 22:31:00 GMT</pubDate>
      <lastBuildDate>Fri, 22 Jun 2007 22:31:00 GMT</lastBuildDate>
    </item>
  </channel>
</rss>
