我很难描述这个问题。也许这就是为什么我很难找到一个好的解决方案(这些词只是不合作)。让我通过代码来解释:
// original code
enum Fruit
{
Apple,
Orange,
Banana,
}
...
Fruit fruit = acquireFruit();
if (fruit != Fruit.Orange && fruit != Fruit.Banana)
coreFruit();
else
pealFruit();
eatFruit();
现在假装这三种类型经历了多年的发展。上述逻辑的不同风格在整个存储过程、SSIS 程序包、Windows 应用程序、Web 应用程序、Java 应用程序、Perl 脚本等中传播....
最后:
// new code
enum Fruit
{
Apple,
Orange,
Banana,
Grape,
}
大多数时候,“系统”运行良好,直到使用 Grapes。然后,系统的某些部分会出现不当行为,在不需要或不需要时对葡萄进行去皮和/或去核。
为了避免这些困惑,您遵循什么样的准则?如果旧代码没有被重构以考虑新的枚举,我的偏好是抛出异常。
我在黑暗中想出了一个办法:
#1 避免这样的“不符合逻辑”
// select fruit that needs to be cored
select Fruit from FruitBasket where FruitType not in(Orange, Banana)
#2 在需要时使用精心构造的 NotIn() 方法
internal static class EnumSafetyExtensions
{
/* By adding enums to these methods, you certify that 1.) ALL the logic inside this assembly is aware of the
* new enum value and 2.) ALL the new scenarios introduced with this new enum have been accounted for.
* Adding new enums to an IsNot() method without without carefully examining every reference will result in failure. */
public static bool IsNot(this SalesOrderType target, params SalesOrderType[] setb)
{
// SetA = known values - SetB
List<SalesOrderType> seta = new List<SalesOrderType>
{
SalesOrderType.Allowance,
SalesOrderType.NonAllowance,
SalesOrderType.CompanyOrder,
SalesOrderType.PersonalPurchase,
SalesOrderType.Allotment,
};
setb.ForEach(o => seta.Remove(o));
// if target is in SetA, target is not in SetB
if (seta.Contains(target))
return true;
// if target is in SetB, target is not not in SetB
if (setb.Contains(target))
return false;
// if the target is not in seta (the considered values minus the query values) and the target isn't in setb
// (the query values), then we've got a problem. We've encountered a value that this assembly does not support.
throw new InvalidOperationException("Unconsidered Value detected: SalesOrderType." + target.ToString());
}
}
现在,我可以安全地使用如下代码:
bool needsCoring = fruit.IsNot(Fruit.Orange, Fruit.Banana);
如果此代码在整个系统中传播,当 Grape 滚进城镇时将抛出异常(qa 将捕捉所有)。
无论如何,这就是计划。这个问题看起来应该很常见,但我似乎无法在谷歌上找到任何东西(可能是我自己的错)。
你们是怎么处理的?
更新:
我觉得这个问题的答案是创建一个“捕获所有其他”机制来停止处理并提醒测试人员和开发人员注意新枚举需要考虑的事实。如果你有“switch ... default”,那就太好了。
如果 C# 没有有 switch ... default,我们可以像这样纠正上面的代码:
Fruit fruit = acquireFruit();
if (fruit != Fruit.Orange && fruit != Fruit.Banana)
coreFruit();
else if(fruit == Fruit.Apple)
pealFruit();
else
throw new NotSupportedException("Unknown Fruit:" + fruit)
eatFruit();
免责声明:
你真的不应该使用上面的任何伪代码。它可能(?)编译甚至工作,但它真的是可怕的代码。如果您正在寻找基于 OOP 的方法,我在此线程中看到了很多不错的解决方案。当然,一个好的解决方案是将所有的切换和检查放在一个集中的方法中(工厂方法让我印象深刻)。除此之外,还需要进行同行代码审查。
最佳答案
如果我正确理解了您的问题,最常见的做法是抛出 NotSupportedException 或 NotImplementedException。
switch (fruit.Kind) {
case Fruit.Apple:
Bite(fruit);
break;
case Fruit.Banana:
FeedToMonkey(fruit);
break;
default: throw new NotSupportedException("Unknown fruit.");
}
至于添加会破坏现有 if-not-is 逻辑的新枚举值,我认为在这种情况下使用枚举是一个糟糕的选择。您的元素显然具有明显不同的行为,它们不像例如颜色。或许最好让选项负责决定如何对待它们。那么你应该用多态替换枚举。
关于c# - 您如何编写其逻辑不受 future 额外枚举影响的代码?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/3833793/
我正在学习如何使用Nokogiri,根据这段代码我遇到了一些问题:require'rubygems'require'mechanize'post_agent=WWW::Mechanize.newpost_page=post_agent.get('http://www.vbulletin.org/forum/showthread.php?t=230708')puts"\nabsolutepathwithtbodygivesnil"putspost_page.parser.xpath('/html/body/div/div/div/div/div/table/tbody/tr/td/div
总的来说,我对ruby还比较陌生,我正在为我正在创建的对象编写一些rspec测试用例。许多测试用例都非常基础,我只是想确保正确填充和返回值。我想知道是否有办法使用循环结构来执行此操作。不必为我要测试的每个方法都设置一个assertEquals。例如:describeitem,"TestingtheItem"doit"willhaveanullvaluetostart"doitem=Item.new#HereIcoulddotheitem.name.shouldbe_nil#thenIcoulddoitem.category.shouldbe_nilendend但我想要一些方法来使用
关闭。这个问题是opinion-based.它目前不接受答案。想要改进这个问题?更新问题,以便editingthispost可以用事实和引用来回答它.关闭4年前。Improvethisquestion我想在固定时间创建一系列低音和高音调的哔哔声。例如:在150毫秒时发出高音调的蜂鸣声在151毫秒时发出低音调的蜂鸣声200毫秒时发出低音调的蜂鸣声250毫秒的高音调蜂鸣声有没有办法在Ruby或Python中做到这一点?我真的不在乎输出编码是什么(.wav、.mp3、.ogg等等),但我确实想创建一个输出文件。
给定这段代码defcreate@upgrades=User.update_all(["role=?","upgraded"],:id=>params[:upgrade])redirect_toadmin_upgrades_path,:notice=>"Successfullyupgradeduser."end我如何在该操作中实际验证它们是否已保存或未重定向到适当的页面和消息? 最佳答案 在Rails3中,update_all不返回任何有意义的信息,除了已更新的记录数(这可能取决于您的DBMS是否返回该信息)。http://ar.ru
我在我的项目目录中完成了compasscreate.和compassinitrails。几个问题:我已将我的.sass文件放在public/stylesheets中。这是放置它们的正确位置吗?当我运行compasswatch时,它不会自动编译这些.sass文件。我必须手动指定文件:compasswatchpublic/stylesheets/myfile.sass等。如何让它自动运行?文件ie.css、print.css和screen.css已放在stylesheets/compiled。如何在编译后不让它们重新出现的情况下删除它们?我自己编译的.sass文件编译成compiled/t
我正在寻找执行以下操作的正确语法(在Perl、Shell或Ruby中):#variabletoaccessthedatalinesappendedasafileEND_OF_SCRIPT_MARKERrawdatastartshereanditcontinues. 最佳答案 Perl用__DATA__做这个:#!/usr/bin/perlusestrict;usewarnings;while(){print;}__DATA__Texttoprintgoeshere 关于ruby-如何将脚
如何在buildr项目中使用Ruby?我在很多不同的项目中使用过Ruby、JRuby、Java和Clojure。我目前正在使用我的标准Ruby开发一个模拟应用程序,我想尝试使用Clojure后端(我确实喜欢功能代码)以及JRubygui和测试套件。我还可以看到在未来的不同项目中使用Scala作为后端。我想我要为我的项目尝试一下buildr(http://buildr.apache.org/),但我注意到buildr似乎没有设置为在项目中使用JRuby代码本身!这看起来有点傻,因为该工具旨在统一通用的JVM语言并且是在ruby中构建的。除了将输出的jar包含在一个独特的、仅限ruby
Rackup通过Rack的默认处理程序成功运行任何Rack应用程序。例如:classRackAppdefcall(environment)['200',{'Content-Type'=>'text/html'},["Helloworld"]]endendrunRackApp.new但是当最后一行更改为使用Rack的内置CGI处理程序时,rackup给出“NoMethodErrorat/undefinedmethod`call'fornil:NilClass”:Rack::Handler::CGI.runRackApp.newRack的其他内置处理程序也提出了同样的反对意见。例如Rack
在rails源中:https://github.com/rails/rails/blob/master/activesupport/lib/active_support/lazy_load_hooks.rb可以看到以下内容@load_hooks=Hash.new{|h,k|h[k]=[]}在IRB中,它只是初始化一个空哈希。和做有什么区别@load_hooks=Hash.new 最佳答案 查看rubydocumentationforHashnew→new_hashclicktotogglesourcenew(obj)→new_has
我想用ruby编写一个小的命令行实用程序并将其作为gem分发。我知道安装后,Guard、Sass和Thor等某些gem可以从命令行自行运行。为了让gem像二进制文件一样可用,我需要在我的gemspec中指定什么。 最佳答案 Gem::Specification.newdo|s|...s.executable='name_of_executable'...endhttp://docs.rubygems.org/read/chapter/20 关于ruby-在Ruby中编写命令行实用程序