iT邦幫忙

2024 iThome 鐵人賽

DAY 2
0

Conditional Complexity

簡介

在程式碼區塊使用不同的冗長的if/else/switch判斷條件, 該函數的複雜度越來越大.

重構前範例

假設有一個作業系統, 帳號有管理員與普通使用者兩種角色. 當普通使用者要求權限, 需要經過層層的權限狀態判斷, 才能轉換到下一個權限狀態, 或者權限狀態被拒絕轉換.
以下是SystemPermission與相關類別的範例

public class SystemPermission
{
    public static readonly string REQUESTED = "REQUESTED";
    public static readonly string CLAIMED = "CLAIMED";
    public static readonly string GRANTED = "GRANTED";
    public static readonly string DENIED = "DENIED";
    public static readonly string UNIX_REQUESTED = "UNIX_REQUESTED";
    public static readonly string UNIX_CLAIMED = "UNIX_CLAIMED";
    private SystemAdmin _admin;
    private SystemProfile _profile;
    private SystemUser _requestor;

    public SystemPermission(SystemAdmin admin, SystemUser requestor, SystemProfile profile)
    {
        _admin = admin;
        _requestor = requestor;
        _profile = profile;
        State = REQUESTED;
        IsGranted = false;
        NotifyAdminOfPermissionRequest();
    }

    public string State { get; private set; }

    public bool IsGranted { get; private set; }
    public bool IsUnixPermissionGranted { get; private set; }


    private void NotifyAdminOfPermissionRequest()
    {
    }

    public void ClaimedBy(SystemAdmin admin)
    {
        if (State != REQUESTED && State != UNIX_REQUESTED)
        {
            return;
        }

        WillBeHandledBy(admin);
        if (State == REQUESTED)
        {
            State = CLAIMED;
        }
        else if (State == UNIX_REQUESTED)
        {
            State = UNIX_CLAIMED;
        }
       
    }

    private void WillBeHandledBy(SystemAdmin admin)
    {
        _admin = admin;
    }

    public void DeniedBy(SystemAdmin admin)
    {
        if (State != CLAIMED && State != UNIX_CLAIMED) return;

        if (_admin != admin) return;

        IsGranted = false;
        IsUnixPermissionGranted = false;
        State = DENIED;
        NotifyUserOfPermissionRequestResult();
    }

    private void NotifyUserOfPermissionRequestResult()
    {
    }

    public void GrantedBy(SystemAdmin admin)
    {
        if (State != CLAIMED && State != UNIX_CLAIMED) return;

        if (_admin != admin) return;

        if (_profile.IsUnixPermissionRequired && State == UNIX_CLAIMED)
        {
            IsUnixPermissionGranted = true;
        }
        else if (_profile.IsUnixPermissionRequired && IsUnixPermissionGranted)
        {
            State = UNIX_REQUESTED;
            NotifyUserOfPermissionRequestResult();
            return;
        }

        State = GRANTED;
        IsGranted = true;
        NotifyUserOfPermissionRequestResult();
    }
}

public class SystemAdmin
{
}

public class SystemProfile
{
    public bool IsUnixPermissionRequired { get; set; } = false;
}

public class SystemUser
{
}

可以看到, GrantedBy()函數需要檢查State、是否為_admin、_profile的IsUnixPermissionRequired與變數IsUnixPermissionGranted等參數判斷.
ClaimedBy()也是依據State條件做這種狀態轉換.

所以, 以這種範例來看, 如果今天狀態更多種, 上述的狀態轉移函數是不是就更複雜?

重構後範例

我們將狀態轉換邏輯成各自的處理類別, 以管理員授權權限的範例, 可以分出1種基礎類別與6種子類別:

public abstract class PermissionState
{
    private readonly string _name;

    protected PermissionState(string name)
    {
        _name = name;
    }

    public static readonly PermissionState REQUESTED = new PermissionRequested();
    public static readonly PermissionState CLAIMED = new PermissionClaimed();
    public static readonly PermissionState GRANTED = new PermissionGranted();
    public static readonly PermissionState DENIED = new PermissionDenied();
    public static readonly PermissionState UNIX_REQUESTED = new UnixPermissionRequested();
    public static readonly PermissionState UNIX_CLAIMED = new UnixPermissionClaimed();

    public override string ToString()
    {
        return _name;
    }

    public virtual void ClaimedBy(SystemAdmin admin, SystemPermission permission)
    {
    }

    public virtual void DeniedBy(SystemAdmin admin, SystemPermission permission)
    {
    }

    public virtual void GrantedBy(SystemAdmin admin, SystemPermission permission)
    {
    }
}

public class PermissionClaimed : PermissionState
{
    public PermissionClaimed() : base("CLAIMED")
    {
    }


    public override void DeniedBy(SystemAdmin admin, SystemPermission permission)
    {
        if (permission.Admin != admin) return;

        permission.IsGranted = false;
        permission.IsUnixPermissionGranted = false;
        permission.PermissionState = DENIED;
        permission.NotifyUserOfPermissionRequestResult();
    }

    public override void GrantedBy(SystemAdmin admin, SystemPermission permission)
    {
        if (permission.Admin != admin) return;
        
        if (permission.IsUnixPermissionDesiredButNotRequested())
        {
            permission.PermissionState = UNIX_REQUESTED;
            permission.NotifyUserOfPermissionRequestResult();
            return;
        }

        permission.PermissionState = GRANTED;
        permission.IsGranted = true;
        permission.NotifyUserOfPermissionRequestResult();
    }
}

public class PermissionDenied : PermissionState
{
    public PermissionDenied() : base("DENIED")
    {
    }
}

public class PermissionGranted : PermissionState
{
    public PermissionGranted() : base("GRANTED")
    {
    }
}

public class PermissionRequested : PermissionState
{
    public PermissionRequested() : base("REQUESTED")
    {
    }

    public override void ClaimedBy(SystemAdmin admin, SystemPermission permission)
    {
        permission.WillBeHandledBy(admin);
        permission.PermissionState = CLAIMED;
    }
}

public class UnixPermissionClaimed: PermissionState
{
    public UnixPermissionClaimed() : base("UNIX_CLAIMED")
    {
    }

    public override void DeniedBy(SystemAdmin admin, SystemPermission permission)
    {
        if (permission.Admin != admin) return;

        permission.IsGranted = false;
        permission.IsUnixPermissionGranted = false;
        permission.PermissionState = DENIED;
        permission.NotifyUserOfPermissionRequestResult();
    }

    public override void GrantedBy(SystemAdmin admin, SystemPermission permission)
    {
        if (permission.Admin != admin) return;

        if (permission.IsUnixPermissionRequestedAndClaimed())
        {
            permission.IsUnixPermissionGranted = true;
        }

        permission.PermissionState = GRANTED;
        permission.IsGranted = true;
        permission.NotifyUserOfPermissionRequestResult();
    }
}

public class UnixPermissionRequested : PermissionState
{
    public UnixPermissionRequested() : base("UNIX_REQUESTED")
    {
    }

    public override void ClaimedBy(SystemAdmin admin, SystemPermission permission)
    {
        permission.WillBeHandledBy(admin);
        permission.PermissionState = UNIX_CLAIMED;
    }
}
  1. 將狀態轉移的處理邏輯, 委託給PermissionState實作, 實作的三大函數:ClaimedBy, DeniedByGrantedBy
  2. 只需要代入SystemAdminSystemPermission兩種物件做判斷, 並在需要時更改這些物件的數值.

SystemPermission重構成:

public class SystemPermission
{
    private SystemAdmin _admin;
    private SystemProfile _profile;
    private SystemUser _requestor;

    public SystemPermission(SystemAdmin admin, SystemUser requestor, SystemProfile profile)
    {
        _admin = admin;
        _requestor = requestor;
        _profile = profile;
        IsGranted = false;
        PermissionState = PermissionState.REQUESTED;
        NotifyAdminOfPermissionRequest();
    }

    public PermissionState PermissionState { get; internal set; }

    public bool IsGranted { get; internal set; }
    public bool IsUnixPermissionGranted { get; internal set; }
    public SystemAdmin Admin => _admin;


    private void NotifyAdminOfPermissionRequest()
    {
    }

    public void ClaimedBy(SystemAdmin admin)
    {
        PermissionState.ClaimedBy(admin, this);
    }

    internal void WillBeHandledBy(SystemAdmin admin)
    {
        _admin = admin;
    }

    public void DeniedBy(SystemAdmin admin)
    {
        PermissionState.DeniedBy(admin, this);
    }

    internal void NotifyUserOfPermissionRequestResult()
    {
    }

    public void GrantedBy(SystemAdmin admin)
    {
        PermissionState.GrantedBy(admin, this);
    }

    internal bool IsUnixPermissionDesiredButNotRequested()
    {
        return _profile.IsUnixPermissionRequired && IsUnixPermissionGranted;
    }

    internal bool IsUnixPermissionRequestedAndClaimed()
    {
        return _profile.IsUnixPermissionRequired && PermissionState == PermissionState.UNIX_CLAIMED;
    }

    internal bool IsClaimedState()
    {
        return PermissionState == PermissionState.CLAIMED || PermissionState == PermissionState.UNIX_CLAIMED;
    }
}

從上述看到, SystemPermission擁有PermissionState物件, 而任何的狀態處理都是委託PermissionState完成.

重構補充說明

這項重構手法是 Replace State-Altering Conditionals with State
是一種善用多型的方式重構.

其他還有
Replace Conditional Logic with Strategy
Move Embellishment to Decorator
Introduce Null Object
也都是處理這種smell的重構手法


上一篇
Code smells簡介與Boolean Blindness的重構
下一篇
Data clump的重構
系列文
程式淨化計畫:痛苦是重構的起源!31
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言