将用于多种方法的一个 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
.
我的情况与下图类似。它是一个 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
.