将用于多种方法的一个 hard-coded 字符串重构为两个

Refactoring one hard-coded string, used in numerous methods, into two

我的情况与下图类似。它是一个 REST API 帮助器 class,它具有针对不同 API 端点的方法并向其提供 header。一个 header,ChannelId,已被硬编码为“频道 1”。

从现在开始,应该可以改用“频道 2”了。重构这个的好策略是什么?我已经提供了我的建议,但感觉这是一种廉价的方式。因为我需要更改大量方法中的签名。

public class RestApiRequestHelper
{
    public void MethodA()
    {
        var request = new RestRequest(RestRequest.GET);
        AddHeaders(request);
        //...

    }

    //... more similar methods using AddHeaders(request)

    public void MethodZ()
    {
        
        var request = new RestRequest(RestRequest.GET);
        AddHeaders(request);
        //...
    }

    private void AddHeaders(RestRequest request)
    {
        request.AddHeader("ChannelId", "Channel 1");
        //...
    }
}

我的建议:

public class RestApiRequestHelper
{
    public void MethodA(string channelId)
    {
        var request = new RestRequest(RestRequest.GET);
        AddHeaders(request, channelId);
        //...
    }

    //... more similar methods using AddHeaders(request)

    public void MethodZ(string channelId)
    {
        var request = new RestRequest(RestRequest.GET);
        AddHeaders(request, channelId);
        //...
    }

    private void AddHeaders(RestRequest request, string channelId)
    {
        request.AddHeader("ChannelId", channelId);
        //...
    }
}

从 API 消费者的角度来看,新版本是一个重大变化,而且确实容易出错。

  • 重大更改:消费者必须采用新的 API,因为它不向后兼容。
  • 容易出错:消费者现在可以用几乎任何东西调用这个 API,例如:null"ThisIsNotExistingChannelId""ThisIsAMisspelledChannelId"await LongRunningOperationToObtainChannelId()、等等

向后兼容性

如果您不想引入重大更改(这样您的客户就可以使用 API 而无需更改任何代码),那么您可以使用可选参数。

有默认值的参数

public void MethodA(string channelId = "Channel 1")
{
   var request = new RestRequest(RestRequest.GET);
   AddHeaders(request, channelId);
   //...
}

具有回退值的参数

public void MethodA(string channelId = default)
{
   var request = new RestRequest(RestRequest.GET);
   AddHeaders(request, channelId ?? "Channel 1");
   //...
}

参数限制

如果您不想允许任何类型的频道标识符,那么您也可以对其进行限制。

运行时检查

private static readonly ImmutableArray<string>
    ValidChannelIds = ImmutableArray.Create("Channel 1", "Channel 2");

public void MethodA(string channelId = default)
{
    if(!ValidChannelIds.Contains(channelId))
        throw new ArgumentOutOfRangeException(nameof(channelId), 
              $"The provided channel is invalid. Valid ids are: '{string.Join(',', ValidChannelIds)}'");
    ...
}

编译时和运行时检查

public enum Channels
{
    [Description("Channel 1")]
    Ch1 = 0,

    [Description("Channel 2")]
    Ch2 = 2,
}

public void MethodA(Channels channelId = Channels.Ch1)
{
    if(!Enum.IsDefined(typeof(Channels), channelId))
        throw new ArgumentOutOfRangeException(nameof(channelId), 
              $"The provided channel is invalid. Valid ids: '{string.Join(',', Enum.GetNames(typeof(Channels)))}'");
    
    var fieldInfo = typeof(Channels).GetField(channelId.ToString("G"));
    var chId = fieldInfo.GetCustomAttribute<DescriptionAttribute>().Description;
    ...

}

Enum.IsDefined 是必需的,因为您也可以这样调用方法:MethodA((Channels)3); 而 3 不会有 Description.