如何在没有强类型的情况下确保可靠的合约?
How to ensure solid contracts without strong typing?
get_min_length()
采用的参数必须匹配 get_pkt_type()
的可能 return 值:
def get_pkt_type(some_val):
"""Determine the type of an XCP packet.
:return:
'CMD' if "Command" packet,
'RES' if "Command Response" packet,
'ERR' if "Error" packet,
'CMD/RES' if uncertain whether a CONNECT CMD or a
RES packet, as their frame bytes can look identical.
:rtype: str
"""
if something:
return 'CMD'
elif something_else2:
return 'RES'
elif something_else3:
return 'ERR'
elif something_else4:
return 'CMD/RES'
def get_min_length(packet_type):
if packet_type in ['CMD', 'RES']:
return 4
elif packet_type in ['ERR', 'CMD/RES']:
return 6
packet_type = get_pkt_type(some_val)
length = get_min_length(packet_type)
我如何确保,如果程序员将新的数据包类型 return 值添加到 get_pkt_type()
,他也不会忘记将值添加到 get_min_length()
。在强类型语言中,packet_type
将是一个已定义的类型,它被 return 编辑并传递,所以我会这样安全。
没有办法确保程序员不会忘记任何事情。程序员也是人,而人往往会忘记事情。
但是您可以向 get_min_length(packet_type)
添加一些代码,从而提醒程序员向该函数添加新值。
else:
print "Unknown packet type. Please add new value to the list."
return some_int_value
或者您甚至可以引发异常,这样程序员会立即看到错误。
PS。在 Python 你必须在调用它们之前定义你的函数。
一般来说,在 Python 中,如果您有一组可扩展的值,那么创建一个浅继承层次结构是有意义的。这不太容易健忘。枚举更适合固定值集。
也就是说,您应该做的第一件事是尾随
raise ValueError("Unexpected enum value")
你的职能。
您可能会考虑的另一件事是使用字典来表示此类映射:
pkt_lengths = {
'CMD': 4,
'RES': 4,
'ERR': 6,
'CMD/RES': 6,
}
get_min_length = pkt_lengths.__getitem__
然后您可以添加一个简单的测试
packet_types = {'CMD', 'RES', 'ERR', 'CMD/RES'}
assert not packet_types.symmetric_difference(pkt_lengths)
如果您经常这样做,请构建一个函数:
def enum_mapper(enum, mapper):
assert not enum.symmetric_difference(mapper)
return mapper.__getitem__
这让你可以做到
get_min_length = enum_mapper(packet_types, pkt_lengths)
并在启动时进行检查。
此外,请考虑使用适当的 enum.Enum
。
你不应该这样做,即使你可以。
Python 是一种 Duck-Typed
语言,在 duck-typed
语言中,我们不会根据变量的类型来限制变量。考虑我定义自定义 packet_type
的情况,如下所示:-
class SpecialPacket(object):
def __init__(self, value):
self.value = value
def __eq__(self, other):
return self.value == other
special_packet = SpecialPacket('CMD')
print(get_min_length(special_packet))
# 6
这就是 duck-typing
的美妙之处,您添加了一个新数据包并且根本不需要更改代码。
回答你的问题 - 编写测试
这就是为什么测试在动态和弱类型语言中被认为非常重要的原因。我们不依赖于强类型并最终编写了比我们需要的更多的代码,而是我们在所有阶段都对我们的代码进行了大量测试。添加新数据包的人有责任确保他的代码正常工作,并且他最终不会破坏你的代码。
您实际上可以在 python 中创建自己的类型 - 类型只是 classes。
class packet_type(object):
def __init__(self, name, length):
self.name = name
self.length = length
CMD = packet_type("CMD", 4)
有时,这似乎有些沉重。一个更简单的替代方法是将属性转储到原始数据结构中,如 Veedrac 的回答。
如果你只想要一个类似结构的容器,一个非常好的中间立场是 namedtuples.
from collections import namedtuple
packet_type = namedtuple('packet_type', ['name', 'length']
CMD = packet_type("CMD", 4)
所有这些选项的优点是参数是直接在对象上定义的,它们实际上属于这些对象。这意味着只有一个点需要定义新参数(在新实例上),而不是像您的设置中那样定义多个。它在 duck-typing 上的表现也更好,因为任何带有 name
和 length
的 class,无论扩展名如何,都可以使用。
get_min_length()
采用的参数必须匹配 get_pkt_type()
的可能 return 值:
def get_pkt_type(some_val):
"""Determine the type of an XCP packet.
:return:
'CMD' if "Command" packet,
'RES' if "Command Response" packet,
'ERR' if "Error" packet,
'CMD/RES' if uncertain whether a CONNECT CMD or a
RES packet, as their frame bytes can look identical.
:rtype: str
"""
if something:
return 'CMD'
elif something_else2:
return 'RES'
elif something_else3:
return 'ERR'
elif something_else4:
return 'CMD/RES'
def get_min_length(packet_type):
if packet_type in ['CMD', 'RES']:
return 4
elif packet_type in ['ERR', 'CMD/RES']:
return 6
packet_type = get_pkt_type(some_val)
length = get_min_length(packet_type)
我如何确保,如果程序员将新的数据包类型 return 值添加到 get_pkt_type()
,他也不会忘记将值添加到 get_min_length()
。在强类型语言中,packet_type
将是一个已定义的类型,它被 return 编辑并传递,所以我会这样安全。
没有办法确保程序员不会忘记任何事情。程序员也是人,而人往往会忘记事情。
但是您可以向 get_min_length(packet_type)
添加一些代码,从而提醒程序员向该函数添加新值。
else:
print "Unknown packet type. Please add new value to the list."
return some_int_value
或者您甚至可以引发异常,这样程序员会立即看到错误。
PS。在 Python 你必须在调用它们之前定义你的函数。
一般来说,在 Python 中,如果您有一组可扩展的值,那么创建一个浅继承层次结构是有意义的。这不太容易健忘。枚举更适合固定值集。
也就是说,您应该做的第一件事是尾随
raise ValueError("Unexpected enum value")
你的职能。
您可能会考虑的另一件事是使用字典来表示此类映射:
pkt_lengths = {
'CMD': 4,
'RES': 4,
'ERR': 6,
'CMD/RES': 6,
}
get_min_length = pkt_lengths.__getitem__
然后您可以添加一个简单的测试
packet_types = {'CMD', 'RES', 'ERR', 'CMD/RES'}
assert not packet_types.symmetric_difference(pkt_lengths)
如果您经常这样做,请构建一个函数:
def enum_mapper(enum, mapper):
assert not enum.symmetric_difference(mapper)
return mapper.__getitem__
这让你可以做到
get_min_length = enum_mapper(packet_types, pkt_lengths)
并在启动时进行检查。
此外,请考虑使用适当的 enum.Enum
。
你不应该这样做,即使你可以。
Python 是一种 Duck-Typed
语言,在 duck-typed
语言中,我们不会根据变量的类型来限制变量。考虑我定义自定义 packet_type
的情况,如下所示:-
class SpecialPacket(object):
def __init__(self, value):
self.value = value
def __eq__(self, other):
return self.value == other
special_packet = SpecialPacket('CMD')
print(get_min_length(special_packet))
# 6
这就是 duck-typing
的美妙之处,您添加了一个新数据包并且根本不需要更改代码。
回答你的问题 - 编写测试
这就是为什么测试在动态和弱类型语言中被认为非常重要的原因。我们不依赖于强类型并最终编写了比我们需要的更多的代码,而是我们在所有阶段都对我们的代码进行了大量测试。添加新数据包的人有责任确保他的代码正常工作,并且他最终不会破坏你的代码。
您实际上可以在 python 中创建自己的类型 - 类型只是 classes。
class packet_type(object):
def __init__(self, name, length):
self.name = name
self.length = length
CMD = packet_type("CMD", 4)
有时,这似乎有些沉重。一个更简单的替代方法是将属性转储到原始数据结构中,如 Veedrac 的回答。 如果你只想要一个类似结构的容器,一个非常好的中间立场是 namedtuples.
from collections import namedtuple
packet_type = namedtuple('packet_type', ['name', 'length']
CMD = packet_type("CMD", 4)
所有这些选项的优点是参数是直接在对象上定义的,它们实际上属于这些对象。这意味着只有一个点需要定义新参数(在新实例上),而不是像您的设置中那样定义多个。它在 duck-typing 上的表现也更好,因为任何带有 name
和 length
的 class,无论扩展名如何,都可以使用。