设计以防止魔术数字和字符串

时间:2015-12-26 12:44:25

标签: c# enums proxy-classes magic-numbers

我正在构建一个从第三方检索并提交给第三方的服务。第三方提供了一个复杂的数据模型,其中包括三种不同的状态类型,所有这些类型的整数都有一个模糊的数字序列。下面提供了一个示例(键:值):

HandlerStatus          CustomerStatus   ArchiveStatus
---                    ---              ---
activeUnallocated:40   draftOpen:0      openOpen:0      
activeAllocated:26     submitted:2      closed:1
activePromoted:102     approved:100
Declined:2             declined:1
...                    ...

只有有限数量的组合,每个组合都有一个术语(提交,委托给另一个用户,等待确认等)。例如:

Combination called "Submitted":
HandlerStatus has to be "activeUnallocated" (40)
CustomerStatus has to be "submitted" (2)
ArchiveStatus has to be "openOpen" (0)

我们可以期待添加其他值,并且用户友好名称可能会随时间而变化 此外,还有一些操作,例如提交和委托给不需要用户选择新值的其他用户,这意味着服务需要知道在发生这些操作时必须设置的一组组合。

  • 我必须能够为每种状态类型
  • 中的每个状态值定义用户友好的显示名称
  • 我必须能够从用户收到新的状态值并映射正确的状态类型
  • 我必须能够查找已定义组合的值
  • 我必须能够根据一组状态值
  • 查找已定义的组合


我想到了三种不同的解决方案,各有利弊

1。为每种状态类型定义en enum

优点:

  • 强类型
  • 维护仅限于一个文件

缺点:

  • 枚举
  • 用于UI演示的字符串格式


2。在外部JSON文件中定义键/值对

优点:

  • 添加不需要更改代码
  • 可以在运行时更新

缺点:

  • 需要具有关联组合的动作的魔术字符串/数字(没有用户输入的操作)
  • 经常从文件系统中读取


3。为不同的可用状态组合创建代理类

优点:

  • 强类型
  • 弹性

缺点:

  • 在第三方系统发生更改时需要代码维护


我已经尝试研究最佳实践和模式,以确定哪种解决方案最适合,可维护性和良好的代码设计,但我没有得到任何地方(同时我在开发过程中严重依赖魔术字符串和数字) )。

2 个答案:

答案 0 :(得分:2)

首先:你在避免使用魔法数字和字符串方面是正确的。

根据经验,这是一个整数,它不会改变(经常)然后枚举是好的。如果它的字符串不经常改变,那么带有字符串的静态(共享)类就是好的。在这些情况下,您的目标是集中定义。这为您提供了几个优点,例如避免拼写错误(在字符串中),使代码更具可读性(使用枚举名称而不是数字)并且您可以跟踪用法(查找用法)。请注意,您可能不希望使用const关键字,除非您的值设置在一块并且永远不会更改。

对于经常更改的值,您需要考虑使用程序逻辑来处理这些值。没有人硬编码客户名称和地址(希望如此),因此如果您的状态经常更改,您希望像处理任何其他数据一样处理它们。如果它们与程序逻辑相关联,那么您需要定义所需的程序逻辑并以某种方式链接状态。

对于频率变化较小的值,您可以选择硬编码(enum / string)和从配置文件中读取。硬编码显然需要重新编译,然后你应该考虑长期成本。你今天花5分钟做什么可能需要几天时间。 2年后,另一位分配了更新其中一个值的开发人员可能需要花费数天的时间来设置正确的开发环境,进行编译和测试。看到从文件中读取值是多么容易,我的建议通常是使用配置文件。

对于您的特定问题,您似乎需要的是翻译表。您的应用程序有一个特定的任务要执行。答案似乎是使应用程序通用并在文件中进行翻译。您希望对文件(或sql-table)中的状态更新(将将哪些状态转换为什么状态)和友好显示(对于用户)保留两种约束。简而言之,使应用程序不知道状态字段的转换是什么。

不要担心文件IO。它易于实现缓存。

private static TranslationObject _translationObject = null;
public static TranslationObject GetTranslationObject() {
    if (_translationObject == null)
       lock (_translationObject)
           _translationObject = JsonConvert.DeserializeObject<TranslationObject>(File.ReadAllTextt("TranslationTable.json"));
    return _translationObject;
}

即使在网络应用程序中,应用程序也会保留一段时间,因此这会在请求之间缓存文件。无论如何,操作系统应该有足够的内存来缓存磁盘IO以获得几千字节的数据。

为了说明实现的众多方法之一,我添加了一个示例,其中结构与Dictionary结合使用,以便在友好状态和其他状态的组合之间进行快速双向查找。我不确定这是否正是您所要求的,但希望它可以提供一些帮助。

void Main()
{
    // Get translation
    var translationObject = GetTranslationObject();
    // Find friendly status based on combo
    var friendly1 = translationObject.ComboStatusToFriendlyStatus[new StatusCombo(0, 30, 5)];
    // Find combo based on friendly status
    var combo1 = translationObject.FriendlyStatusToComboStatus[0];
}

public struct StatusCombo
{
    // Please note that fields are readonly for immutability.
    // This is particularly important since the GetHashCode() value is used in dictionaries.

    // Note that status fields can also be strings (because we use .GetHashCode() in GetHashCode()).
    public readonly int Status1;
    public readonly int Status2;
    public readonly int Status3;
    [JsonConstructor]
    public StatusCombo(int status1, int status2, int status3)
    {
        Status1 = status1;
        Status2 = status2;
        Status3 = status3;
    }

    public override int GetHashCode()
    {
        unchecked
        {
            int hashCode = Status1.GetHashCode();
            hashCode = (hashCode * 397) ^ Status2.GetHashCode();
            hashCode = (hashCode * 397) ^ Status3.GetHashCode();
            // ... Repeat for every extra statuscode you add
            return hashCode;
        }
    }
}

public class TranslationObject
{
    public Dictionary<int, string> Status1Mapping;
    public Dictionary<int, string> Status2Mapping;
    public Dictionary<int, string> Status3Mapping;
    public Dictionary<int, string> FriendlyStatus;

    public Dictionary<int, StatusCombo> FriendlyStatusToComboStatus;
    [JsonIgnore]
    public Dictionary<StatusCombo, int> ComboStatusToFriendlyStatus;
}

private static TranslationObject _translationObject = null;
public static TranslationObject GetTranslationObject()
{
    if (_translationObject == null)
        lock ("Reading _translationObject")
        {
            _translationObject = JsonConvert.DeserializeObject<TranslationObject>(File.ReadAllText(@"TranslationTables.json"));
            // Populate reverse lookup
            _translationObject.ComboStatusToFriendlyStatus=new Dictionary<UserQuery.StatusCombo, int>();
            foreach (var t in _translationObject.FriendlyStatusToComboStatus)
                _translationObject.ComboStatusToFriendlyStatus.Add(t.Value, t.Key);

        }
    return _translationObject;
}

示例JSON文件:

{
  "Status1Mapping": {
    "0": "Status1_0",
    "10": "Status1_1"
  },
  "Status2Mapping": {
    "30": "Status2_0",
    "55": "Status2_1"
  },
  "Status3Mapping": {
    "5": "Status3_0",
    "2": "Status3_1"
  },
  "FriendlyStatus": {
    "0": "Submitted",
    "1": "Received"
  },
  "FriendlyStatusToComboStatus": {
    "0": {
      "Status1": 10,
      "Status2": 55,
      "Status3": 2
    },
    "1": {
      "Status1": 0,
      "Status2": 30,
      "Status3": 5
    }
  }
}

我用来填充示例JSON的代码:

var tro = new TranslationObject();
tro.Status1Mapping = new Dictionary<int, string>();
tro.Status2Mapping = new Dictionary<int, string>();
tro.Status3Mapping = new Dictionary<int, string>();

tro.Status1Mapping.Add(0, "Status1_0");
tro.Status1Mapping.Add(10, "Status1_1");
tro.Status2Mapping.Add(30, "Status2_0");
tro.Status2Mapping.Add(55, "Status2_1");
tro.Status3Mapping.Add(5, "Status3_0");
tro.Status3Mapping.Add(2, "Status3_1");

tro.FriendlyStatus = new Dictionary<int, string>();
tro.FriendlyStatus.Add(0, "Submitted");
tro.FriendlyStatus.Add(1, "Received");

tro.FriendlyStatusToComboStatus = new Dictionary<int, UserQuery.StatusCombo>();
tro.FriendlyStatusToComboStatus.Add(0, new StatusCombo(10, 55, 2));
tro.FriendlyStatusToComboStatus.Add(1, new StatusCombo(0, 30, 5));

File.WriteAllText(@"TranslationTables.json", JsonConvert.SerializeObject(tro));

答案 1 :(得分:0)

您可以在json,xml,resx,纯文本,sql或任何您想要的内容中定义键值对。将三个状态字段组合为字符串键:

key               value
A2|B3|            Submitted
A2|B3|X           Submitted with error
A1|B1|            Started
etc...

然后在应用程序启动时,读取此文件并将其存储在字典中。

Dictionary<string, string> StatusTypes = new Dictionary<string, string>();

然后只有专业人士:

  • 如单独文件中所定义的那样灵活
  • 新状态无需更改代码
  • 快速,内存中没有持续的磁盘访问