工具契约、执行循环与副作用:练习答案
练习解答
以下是完整离线文件,保存为answer.py后运行python answer.py;先建立与正文一致的初始状态,再验证变体。
使用正文定义与最终状态:
python
import json
orders={};stock={'A':3}
def reserve(principal,key,args):
if principal!='alice':raise PermissionError('forbidden')
if set(args)!={'sku','quantity'} or args['sku']!='A':raise ValueError('bad args')
n=args['quantity']
if type(n) is not int or n<=0:raise ValueError('bad quantity')
fingerprint=json.dumps(args,sort_keys=True)
cache_key=(principal,key)
if cache_key in orders:
old=orders[cache_key]
if old['fingerprint']!=fingerprint:raise ValueError('idempotency conflict')
return old['result']
if stock['A']<n:raise ValueError('insufficient stock')
stock['A']-=n
result={'reserved':n,'remaining':stock['A']}
orders[cache_key]={'fingerprint':fingerprint,'result':result}
return result
first=reserve('alice','order-1',{'sku':'A','quantity':2})
assert reserve('alice','order-1',{'sku':'A','quantity':2})==first
assert stock['A']==1
try:reserve('alice','order-1',{'sku':'A','quantity':1})
except ValueError as e:assert str(e)=='idempotency conflict'
else:raise AssertionError('conflicting replay accepted')
print('one reservation',stock)
try:reserve('bob','order-1',{'sku':'A','quantity':2})
except PermissionError:pass
else:raise AssertionError('unauthorized replay')
try:reserve('alice','order-2',{'sku':'A','quantity':2})
except ValueError as e:assert str(e)=='insufficient stock'
else:raise AssertionError('oversold')
assert stock['A']==1
print('failure preserves inventory')失败路径也要验证状态未变,只断言“抛了异常”无法证明没有先扣库存后失败。