
В недавнем проекте мне была поставлена задача рефакторинга больших частей веб-системы. Она написана на 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();
}
}
В итоге мне нравится это решение больше, чем оригинальное. Один из недостатков - уровень, на котором оно написано. Я хотел создать более простое решение, которое мог бы поддерживать любой разработчик. В приведенном выше коде нет ничего сложного; я создаю коллекцию и итерируюсь по ней. Путаница возникает с оценкой и выражениями. Это не тема для начинающих.
Автор: Чак Конвей специализируется на разработке программного обеспечения и генеративном ИИ. Свяжитесь с ним в социальных сетях: X (@chuckconway) или посетите его на YouTube.