В недавнем проекте мне было поручено провести рефакторинг больших частей веб-системы. Она написана на 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.