Skip to content

Посты

Рефакторинг кода

22 октября 2010 г. • 6 мин чтения

Рефакторинг кода

В недавнем проекте мне было поручено провести рефакторинг больших частей веб-системы. Она написана на C#. Со временем некоторые файлы code-behind выросли до 4000 строк. Цель была снизить это число до более управляемого уровня.

В следующих нескольких постах я взял фрагменты кода, которые я рефакторил, и объясню свои мысли и то, как я пришел к решению.

Первый фрагмент кода:

    string tmp = Request.QueryString["st"];
    _varStartRecNum = tmp;
    if ((tmp != null) & (!Page.IsPostBack))
    {
        _varStartRecNum = tmp;
        postBack = true;
    }

    tmp = Request.QueryString["det"];
    if ((tmp != null) & (!Page.IsPostBack))
    {
        _varDetailsRecNum = tmp;
        postBack = true;
    }

    tmp = Request.QueryString["return"];
    if ((tmp != null) & (!Page.IsPostBack))
    {
        postBack = true;
    }

    tmp = Request.QueryString["searchnow"];
    if ((tmp != null) & (!Page.IsPostBack))
    {
        Session["selectedTab"] = "mtf";
        Session["sessionDSProviders"] = null;
        Session["mtfs"] = null;
    }

    tmp = Request.QueryString["displaywalking"];
    if (tmp == "true")
    {
        dispMtf = false;
        postBack = true;
    }

    tmp = Request.QueryString["sb"];

    if ((tmp != null) & (!Page.IsPostBack))
    {
        _varSortBy = tmp;
        postBack = true;
        switch (_varSortBy)
        {
            case "Distance":
            case "Drive time":
                ddlSortBy.SelectedIndex = 0;
                break;
            case "Name":
                ddlSortBy.SelectedIndex = 1;
                break;
            case "Gender":
                ddlSortBy.SelectedIndex = 2;
                break;
            case "Clinic":
                ddlSortBy.SelectedIndex = 3;
                break;
            case "City":
                ddlSortBy.SelectedIndex = 4;
                break;
            case "Description":
                ddlSortBy.SelectedIndex = 5;
                break;
        }
    }

Приведенный выше фрагмент кода представляет собой набор операторов if, которые являются оценкой и выполнением. В моей первой попытке я попытался использовать одну и ту же оценку для всех операторов if, но затем я понял, что одна была другой. Не понимая намерения кода, я вынужден сохранить логику в точности.

Другая оценка if:

tmp = Request.QueryString["displaywalking"];
if (tmp == "true")
{
    dispMtf = false;
    postBack = true;
}

Оператор switch вызвал у меня беспокойство. Условие для входа в оператор switch такое же, как и у других. Я решил продолжить и беспокоиться об операторе switch позже.

Код использует одну и ту же переменную, переменную ‘tmp’, для получения различных значений запроса. Значение перезаписывается при каждом получении значения запроса. Для ясности я создал переменную для каждого значения запроса:

string st = Request.QueryString["st"];
string det = Request.QueryString["det"];
string @return = Request.QueryString["return"];
string searchNow = Request.QueryString["searchnow"];
string displayWaling = Request.QueryString["displaywalking"];
string sb = Request.QueryString["sb"];

Следующий шаг заключался в изоляции оценки и выражения, сохраняя их связь друг с другом. Если оценка верна, я хочу выполнить соответствующее выражение. Я создал класс, который представлял эту связь.

private class Evaluate
{

    public Func Evaluation { get; set; }

    public Action Expression { get; set; }
}

Теперь я могу создать оценку, и если она верна, я могу выполнить ее выражение.

Следующая проблема заключалась в том, как использовать приведенный выше класс со всеми операторами if. Я беспокоился, что выражения могут стать громоздкими в коллекции. Вся цель заключалась в создании лаконичного масштабируемого решения. Существующее решение не было ни тем, ни другим.

var eval = new[]
               {
                   new Evaluate {Evaluation = () => (!string.IsNullOrEmpty(st) && !IsPostBack), Expression = () => { _varStartRecNum = st;postBack = true; }},
                   new Evaluate {Evaluation = () => (!string.IsNullOrEmpty(det) && !IsPostBack), Expression = () => { _varStartRecNum = det;postBack = true; }}, 
                   new Evaluate {Evaluation = () => (!string.IsNullOrEmpty(@return) && !IsPostBack), Expression = () => {postBack = true; }}, 
                   new Evaluate {Evaluation = () => (!string.IsNullOrEmpty(searchNow) && !IsPostBack), Expression = () => {Session["selectedTab"] = "mtf";Session["sessionDSProviders"] = null; Session["mtfs"] = null;}}, 
                   new Evaluate {Evaluation = () => (!string.IsNullOrEmpty(displayWaling)), Expression = () => {dispMtf = false; postBack = true;}}, 
                   new Evaluate {Evaluation = () => (!string.IsNullOrEmpty(sb) && !IsPostBack), Expression = () => {_varSortBy = sb;postBack = true; SetSort(_varSortBy);}}, 
               };

Получилось лучше, чем я ожидал. Один недостаток моего решения в том, что если вы не знаете, как использовать делегаты, вы будете в затруднении при обслуживании приведенного выше кода.

Последнее препятствие было с оператором switch. Он не мог бы хорошо вписаться в мою анонимную коллекцию, но тогда ему это и не требовалось:

private void SetSort(string sortBy)
{
    switch (sortBy)
    {
        case "Distance":
        case "Drive time":
            ddlSortBy.SelectedIndex = 0;
            break;
        case "Name":
            ddlSortBy.SelectedIndex = 1;
            break;
        case "Gender":
            ddlSortBy.SelectedIndex = 2;
            break;
        case "Clinic":
            ddlSortBy.SelectedIndex = 3;
            break;
        case "City":
            ddlSortBy.SelectedIndex = 4;
            break;
        case "Description":
            ddlSortBy.SelectedIndex = 5;
            break;
    }
}

Инкапсулировав его в метод, я смог ссылаться на метод в выражении. Это сработало очень хорошо.

new Evaluate {Evaluation = () => (!string.IsNullOrEmpty(sb) && !IsPostBack), Expression = () => {_varSortBy = sb;postBack = true; SetSort(_varSortBy);}

Последний компонент — это итерация по коллекции:

foreach (var evaluate in eval.Where(evaluate => evaluate.Evaluation()))
{
    evaluate.Expression();
}

Полное решение:

private class Evaluate
{
    public Func Evaluation { get; set; }

    public Action Expression { get; set; }
}

private void SetSort(string sortBy)
{
    switch (sortBy)
    {
        case "Distance":
        case "Drive time":
            ddlSortBy.SelectedIndex = 0;
            break;
        case "Name":
            ddlSortBy.SelectedIndex = 1;
            break;
        case "Gender":
            ddlSortBy.SelectedIndex = 2;
            break;
        case "Clinic":
            ddlSortBy.SelectedIndex = 3;
            break;
        case "City":
            ddlSortBy.SelectedIndex = 4;
            break;
        case "Description":
            ddlSortBy.SelectedIndex = 5;
            break;
    }
}

private void EvaluateQueryParameters()
{
    string st = Request.QueryString["st"];
    string det = Request.QueryString["det"];
    string @return = Request.QueryString["return"];
    string searchNow = Request.QueryString["searchnow"];
    string displayWaling = Request.QueryString["displaywalking"];
    string sb = Request.QueryString["sb"];

    var eval = new[]
                   {
                       new Evaluate {Evaluation = () => (!string.IsNullOrEmpty(st) && !IsPostBack), Expression = () => { _varStartRecNum = st;postBack = true; }},
                       new Evaluate {Evaluation = () => (!string.IsNullOrEmpty(det) && !IsPostBack), Expression = () => { _varStartRecNum = det;postBack = true; }}, 
                       new Evaluate {Evaluation = () => (!string.IsNullOrEmpty(@return) && !IsPostBack), Expression = () => {postBack = true; }}, 
                       new Evaluate {Evaluation = () => (!string.IsNullOrEmpty(searchNow) && !IsPostBack), Expression = () => {Session["selectedTab"] = "mtf";Session["sessionDSProviders"] = null; Session["mtfs"] = null;}}, 
                       new Evaluate {Evaluation = () => (!string.IsNullOrEmpty(displayWaling)), Expression = () => {dispMtf = false; postBack = true;}}, 
                       new Evaluate {Evaluation = () => (!string.IsNullOrEmpty(sb) && !IsPostBack), Expression = () => {_varSortBy = sb;postBack = true; SetSort(_varSortBy);}}, 
                   };

    foreach (var evaluate in eval.Where(evaluate => evaluate.Evaluation()))
    {
        evaluate.Expression();
    }
}

В конце концов, мне нравится это решение больше, чем оригинальное. Один из недостатков — это уровень, на котором оно написано. Я хотел создать более простое решение, которое любой разработчик мог бы обслуживать. В приведенном выше коде нет ничего сложного; я создаю коллекцию и итерирую по ней. Путаница возникает с оценкой и выражениями. Это не тема для начинающих.

Автор: Chuck Conway — инженер AI с почти 30-летним опытом разработки программного обеспечения. Он создает практические системы AI — конвейеры контента, агенты инфраструктуры и инструменты, которые решают реальные проблемы — и делится тем, что он узнает на этом пути. Свяжитесь с ним в социальных сетях: X (@chuckconway) или посетите его на YouTube и на SubStack.

↑ Вернуться в начало

Вам также может понравиться