首页
学习
活动
专区
圈层
工具
发布
社区首页 >问答首页 >链梯法的精算求偿

链梯法的精算求偿
EN

Code Review用户
提问于 2019-12-28 13:05:58
回答 1查看 136关注 0票数 1

大家好,在此之前,非常感谢大家为帮助他人而付出的时间。

这是我在这里的第一篇文章,所以我希望我遵守指导方针。也许我有一点背景:我没有正规的计算机科学教育,除了一些大学的基础入门课程。我在软件开发方面做了一年的实习生,直到整个团队因为管理决定而被解雇。在此期间,我主要为公司的ERP系统做了一些编程。由于ERP开发与“常规”编程有很大不同,我(认为我)知道的几件事都是自学的,所以它们很容易出错。现在,我在一家保险公司担任非IT角色,但我想做一些与我的新工作相关的编程手指练习,从而创建了一个用于精算索赔的小型类库,用链梯法进行保留,其他的也可能被添加进来。整个存储库可以在我的集散地上找到。

嗯,对于我的问题:

名称空间:目前,我已经按主题对源文件进行了拆分,例如异常、保留方法和底层模型。

代码语言:javascript
复制
namespace ActuarialMaths.NonLife.ClaimsReserving.Model
namespace ActuarialMaths.NonLife.ClaimsReserving.Methods
namespace ActuarialMaths.NonLife.ClaimsReserving.Exceptions

虽然拆分在项目内部是有意义的,但在我看来,名称空间对于另一个项目所使用的库来说太细了。另一方面,将异常、模型和方法抛到一个名称空间中,可能会在某个时候变得过于混乱。在这种情况下,有什么更好的解决办法呢?

数据结构:我从.NET堆栈实现中获得了一些灵感,并对运行三角形进行了建模,分别用一个锯齿状的二维数组对底层付费索赔进行建模,该数组的初始容量在需要时增加了一倍。

代码语言:javascript
复制
public abstract class Triangle
{
    private const int initialCapacity = 8;
    protected decimal[][] claims;
    private int capacity;

    protected Triangle(int periods)
    {
        capacity = initialCapacity;
        while (capacity < periods)
        {
            capacity *= 2;
        }
        claims = new decimal[capacity][];

        for (int i = 0; i < capacity; i++)
        {
            claims[i] = new decimal[capacity - i];
        }

        Periods = periods;
    }

    public virtual void AddClaims(IEnumerable<decimal> values)
    {
        Periods++;

        if (capacity < values.Count())
        {
            capacity *= 2;

            Array.Resize(ref claims, capacity);

            for (int i = 0; i < capacity; i++)
            {
                Array.Resize(ref claims[i], capacity - i);
            }
        }
    }

首先,数组只用于存储值。不需要花哨的开销,例如一张清单。这些值只需要通过一些索引来设置和检索,无论是按行、列,还是按对角。另一方面,将三角形建模为表示对角线的其他列表的列表将使三角形上的基本操作更加容易,例如简单地在给定索引上设置/返回/添加集合,而不是摆弄for-/foreach-循环和递增/递减索引。当前实现是一个有效的想法,还是可读性在这里忽略了不必要的开销?

接口:在上面提到的工作中,我以某种方式开发了(坏的?)为所有内容创建接口的习惯,因此我的代码的初始版本顶部有一个额外的接口:

代码语言:javascript
复制
public interface ITriangle : ISliceable<decimal>, ICloneable
{
    int Periods { get; }
    void AddClaims(IEnumerable<decimal> values);
}

然后,抽象类三角形实现了接口ITriangle。半程后,我意识到在这个特定的领域中根本没有实现基本运行三角形的其他方法,并删除了接口,从而删除了抽象层。虽然保留方法不一定需要知道三角形是如何实现的,只要它提供了必要的方法和属性,那么在处理包含某种逻辑的对象时,是否真的需要始终针对接口进行编程,尽管不存在(至少是明显的)可能以另一种方式实现它的可能性吗?对于“逃逸方”也是如此,那里甚至不需要抽象类。

方法命名:例如,GetColumn(int column)SetColumn(IEnumerable<decimal> values, int column)在ISliceable接口中需要它们的主要形容词才能告诉任何人它们在做什么,而在处理保留方法时,我在命名检索计算值的方法时忽略了它们,例如检索总储备的方法称为TotalReserve()而不是GetTotalReserve()。我认为在所有东西前面加上"Get"s会使代码混乱,但现在,在我看来,这在某种程度上混淆了这些方法的实际工作。是否应该有“更清晰”的名称,尽管手动设置这些值是不可能的,并且应该清楚这些方法是用来检索值的?

方法的放置:虽然CalculateReserves()CalculateCashflows()方法实际上在正方形上是有意义的,因为它们只需要存储在正方形中的值,但是当平方中的索赔/损失不是由储备方法从三角形发展而来时,它们就完全没有意义。另一方面,在保留方法中,即使在抽象基类中,当我添加基于模拟的方法时,也会导致代码重复。将方法放在“它正在处理的”对象上更好,还是放在提供使方法有意义的执行成为可能的逻辑的类上呢?

备忘:在保留方法中,基本上所有的东西都是回忆录,例如:

代码语言:javascript
复制
public Square Projection()
{
    if (projection == null)
    {
        projection = CalculateProjection();
    }

    return projection;
}

虽然这对于像上面的投影这样的更复杂的事情来说是有意义的,但在我看来,做一些计算似乎有点不合适。

代码语言:javascript
复制
public IEnumerable<decimal> CalculateCashflows()
{
    for (int i = 0; i < Periods - 1; i++)
    {
        int diagonal = Periods + i;

        yield return GetDiagonal(diagonal)
            .Take(Periods - 1 - i)
            .Zip(GetDiagonal(diagonal - 1), (x, y) => x - y).Sum();
    }
}

我会说,如果列很长的话,这是有意义的,但是考虑到域,它很少超过20个元素,所以每次需要的时候都重新计算它,而不是太昂贵。那么,将计算出来的值存储在私有类数组中而不是重新计算它是否明智?

提前感谢!如果有人想要查看整个代码并注意到那里的一些东西,我会非常感谢建设性的批评。

EN

回答 1

Code Review用户

回答已采纳

发布于 2019-12-29 05:15:58

名称空间模型很好,但是方法和异常不应该在名称空间中。异常,应该为每个类处理。因此,如果您将所有异常移到一个名称空间Exceptions下,这意味着Exceptions名称空间将在所有类(整个项目)中被引用。所以,最好避免那样做。对于方法命名空间,不确定您打算使用它做什么,但是当然,将它重命名为更具体的名称会更好,例如,如果您指的是Methods作为扩展或助手方法,您可以将其重命名为UtilUtility或任何有意义的东西。在项目的开始阶段,不要过多地考虑它,因为您将更好地了解应该存在什么名称空间。

对于抽象类,除了提供的逻辑之外,我不完全了解它的实际使用情况,但是将它转换为List<IEnumerable<decimal>>似乎是正确的。它将给你比现在的二维数组更多的优势。首先,它将消除维持容量的需要。其次,它会缩短时间,让我们集中精力于实际的逻辑来管理数据。因此,如果您将其转换为list,并将基本功能添加到您的抽象中,它应该会给您提供如下内容:

代码语言:javascript
复制
public abstract class Triangle : IEnumerable<IEnumerable<decimal>>
{
    private readonly List<IEnumerable<decimal>> claims = new List<IEnumerable<decimal>>();

    protected Triangle()
    {
        // since you're using List<IEnumerable<decimal>>, there is no need to maintain the capacity, unless if you need a fixed capacity
    }

    public IEnumerable<decimal> this[int index]
    {
        get => claims[index];
        set => claims[index] = value;
    }

    public int Count => claims.Count;


    public void Add(IEnumerable<decimal> values)
    {
        claims.Add(values);
    }

    public void Clear()
    {
        claims.Clear();
    }

    public bool Contains(IEnumerable<decimal> values)
    {
        return claims.Contains(values);
    }

    public void Insert(int index, IEnumerable<decimal> values)
    {
        claims.Insert(index, values);
    }

    public void Remove(int index)
    {
        claims.RemoveAt(index);
    }

    public int RemoveAll(Predicate<IEnumerable<decimal>> match)
    {
        return claims.RemoveAll(match);
    }

    public IEnumerator<IEnumerable<decimal>> GetEnumerator()
    {
        return claims.GetEnumerator();
    }

    IEnumerator IEnumerable.GetEnumerator()
    {
        return claims.GetEnumerator();
    }
}

我选择实现IEnumerable<IEnumerable<decimal>>接口,使用foreach循环和Linq扩展增强类的功能。

但是,您不应该删除接口,因为实现接口从来都不是坏习惯,实际上,建议在可能与其他类(具体类或抽象类)一起重用的所有类上使用接口。例如,如果其他开发人员想要实现继承Triangle的类,他们可以实现它的接口、抽象类或具体类。有时,类需要继承多个类,除非我们使用它们的接口,否则无法直接继承它们。因此,始终为类保留一个接口,并在类上实现它。您还应该保留该接口所需的最低功能。您可以在类中实现其他接口,也可以在接口上实现它们,然后在类上实现接口。这两个选项都是有效的选项。

对于命名约定,有时您需要将函数包装在新的命名约定下,以使其他开发人员也能理解和清楚。因此,使用GetSet前缀作为命名约定是没有什么错的,它使事情在大多数情况下更容易读懂,但有时实现会迫使您根据业务逻辑需求使用不同的命名约定,在这种情况下,您可能希望保留所需的命名公示,并且它可以回调私有的setter和getter方法。例如,您可以这样做:

代码语言:javascript
复制
public abstract class Triangle
{
    public IEnumerable<decimal> this[int index] 
    {
        get => GetColumn(index);
        set => SetColumn(value, index);
    }

    public void Add(IEnumerable<decimal> values, int index)
    {
        SetColumn(IEnumerable<decimal> values, int index);
    }

    protected IEnumerable<decimal> GetColumn(int index) { ... }

    protected void SetColumn(IEnumerable<decimal> values, int index) { ... }
}

然后,它就可以像:

代码语言:javascript
复制
var test = new Triangle(); 
var column = new List<decimal> { 4.7m, 68.36m, 889.14m };

test[0] = column;
// or 
test.Add(column);

这些方法的位置。如果您看到有一个方法脱离了类的功能(如CalculateReserves()),您可以将它移到与当前类链接的另一个类中(例如,您可以创建一个计算类,作为包含所有计算方法的扩展类,并且可以对它们进行排序。(例如,test.Calculate().Cashflows().Sum())。此API将应用于所有三角形,正方形,..etc。所以,是的,这是可能的,也是可行的。

那么,将计算出来的值存储在私有类数组中而不是重新计算它是否明智?

是的,也可以有一个私有属性,它保存了附加值的总和,因此在Add()方法中,您可以在私有属性_total += value;中添加每个新值,并在需要它的地方检索它。这种方法将加快计算部分的速度。如果你有多重总数(比如现金流,费用,..etc )。总之,您可以将它们存储在列表、数组或字典中。如果这些都是很少的和(假设您只有5种不同类型的和要存储),那么使用5个变量(每个变量一个)来避免使用额外的集合。

用你对代码的正确判断。

票数 1
EN
页面原文内容由Code Review提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://codereview.stackexchange.com/questions/234733

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档