<?xml version="1.0" encoding="GB2312" ?>
<?xml-stylesheet type="text/xsl" href="../../article.xsl" ?>

<article>

<title>软件开发中的矛盾——一个简单的例子</title>

<author>晨光（Morning）</author>

<keywords>
  <keyword>软件开发</keyword>
  <keyword>矛盾</keyword>
</keywords>

<from type="原作"/>
<copyright/>

<paragraph>
在以前的文章中，我曾经提到过软件开发中充满了矛盾，一些原则本身就是彼此矛盾的，需要不断在这些矛盾中寻求折中、平衡。这里给出一个源自实际的简单例子，希望能给大家一点启示，只是不知道是否贴切（说明：程序用C++语言描述，T为某个数据类型）。
</paragraph>

<paragraph>
在定义某个类的接口时，需要定义两个相关变量a和b的getter/setter函数。为了使接口尽量精简，我们采用第一种方法，用一对getter/setter来处理:
<code>
getAB(T* pa, T* pb);
setAB(T a, T b);
</code>
</paragraph>

<paragraph>
但是，如果有时只想对其中一个变量进行存取时，调用getAB/setAB就需要额外的工作，client方的代码就会很累赘。
</paragraph>

<paragraph>
为了获取a的值，不得不定义一个额外的变量b：
<code>
T a, b;
getAB(&amp;a, &amp;b);
cout &lt;&lt; a;	// only get a
</code>
</paragraph>

<paragraph>
为了设置a的值而保留b的值，不得不额外调用getAB：
<code>
T a, b;
getAB(&amp;a, &amp;b);
a = 10;
setAB(a, b);	// only set a
</code>
</paragraph>

<paragraph>
为此，我们采用第二种方法，将之拆成两对函数：
<code>
T getA();
T getB();
void setA(T a);
void setB(T b);
</code>
</paragraph>

<paragraph>
这样，接口就一下扩展了一倍。但是，事情并未就此结束。有时，像getAB/setAB这样的方式并非只起到了简化接口的作用。在调用setAB的时候，我们可以从a和b相关的角度来考察a和b的合法性，比如：a,b代表某个值域的上下限，那么假定如果a > b时，设置就不合理，就应该拒绝。而这种合法性检查用第二种方法实现的时候就不是那么顺利了，粗看起来代码应该如下：
<code>
int setA(T a)
{
  if (a &gt; m_b)
    return -1;	// error
  else
    m_a = a;
}

int setB(T b)
{
  if (b &lt; m_a)
    return -1;	// error
  else
    m_b = b;
}
</code>
</paragraph>

<paragraph>
如果只是设置a、b中的一个值，倒不会有任何麻烦，这种方法完全胜任。但是，如果同时设置呢？暂且不考虑a、b的初值应该如何取，比如某次对a、b的设置使a、b分别等于5、10，而再次试图重设a、b为15、20时，问题就产生了：
<code>
setA(15);
setB(20);
</code>
</paragraph>

<paragraph>
结果变成了a = 5, b = 20(completely error)。
</paragraph>

<paragraph>
如果要得到正确结果，则需要颠倒调用setA和setB的次序。但是，如果a、b要分别设成1、6呢？亦即，为了保证成功设置，client代码需要十分小心，不同情况，采用不同的调用约定。对于上述情况，用第二种方法很难做到正确的合法性检查，因为函数的signature决定了它无法得知相关的另一个值，从而不能做出正确判断。
</paragraph>

<paragraph>
所以，事情的演变过程就是：
<list>
  <li>为了使接口精简，我们选择方法一；</li>
  <li>为了不增加额外的客户代码，我们选择方法二；</li>
  <li>为了进行合法性检查，我们又不得不选择方法一。</li>
</list>
这里总共出现了两种方法，三个原则（“为了……”）。
</paragraph>

<paragraph>
而当我们最终决定选择方法二时，还是有可能背负着“增加额外的客户代码”这样的罪名。而如果你确实不想如此，或许你会将两种方法结合使用，即把getA/setA，getB/setB，getAB/setAB统统定义为接口。可是，你又可能会被人指责为“接口混乱”，而且对于setA/setB而言，合法性检查仍然是个问题。
</paragraph>

<paragraph>
结论：在有多种方法可供选择时，存在不同的原则（选择依据），针对实际的情况，我们需要作出决定。这种决定往往不会做到满足所有原则，但一般它应该是最大限度的适合大多数情形。如果，实际的情况不能足以使你作出很肯定的判断，那么，恐怕只有习惯和直觉可以影响你的决定了。只是，或许以后你还会修改你的决定。
</paragraph>

<paragraph>
ps:为了说明方便，所以这里选用了一个极为简单的例子。坦白讲，可能有夸大之嫌。实际情况下，对于这样的“getter/setter”问题，你多半不会像文中说得那样处于如此为难的境地，除非你是个完美主义者。你可以很快作出决定，因为即使无法满足某条原则，其代价也不会很高。否则，那些软件开发人员，每天就不用写几行代码了，而且会深陷于矛盾的痛苦之中。但是，在这样细微的地方都会存在矛盾，可以想见，用“矛盾重重”来形容软件开发过程，可能是不算夸张的。
</paragraph>

<paragraph>
注：本文已在csdn上贴出，查看相关评论请到<link href="http://www.csdn.net/develop/article/14/14679.shtm">这里</link>
</paragraph>

</article>