Исходный код
public TourResult NextTour()
{
TourResult lresult = new TourResult();
lresult.TourNum = mCurrentTour;
lresult.IsEnd = false;
Card.Face[] lCurrFaces = getCurrentTourFace();
int lMaxFace = 0;
foreach (Card.Face lFace in lCurrFaces)
{
lMaxFace = ((int)lFace) > lMaxFace ? (int)lFace : lMaxFace;
}
int lMaxFaceCount = 0;
int lNotZeroCount = 0;
foreach (Card.Face lFace in lCurrFaces)
{
lMaxFaceCount = (int)lFace == lMaxFace ? lMaxFaceCount + 1 : lMaxFaceCount + 0;
lNotZeroCount = (int)lFace != 0 ? lNotZeroCount + 1 : lNotZeroCount + 0;
}
if (lMaxFaceCount > 1)
{
mUnknowTour = mUnknowTour > -1 ? mUnknowTour : mCurrentTour;
mCurrentTour++;
lresult.Message = "Одинаковые карты, надо разиграть";
lresult.PlayerIndex = -1;
lresult.Face = (Face)lMaxFace;
return lresult;
}
int lMaxFaceIndex;
for (lMaxFaceIndex = 0; lMaxFaceIndex < lCurrFaces.Length; lMaxFaceIndex++)
{
if (((int)lCurrFaces[lMaxFaceIndex]) == lMaxFace)
break;
}
if (lNotZeroCount < 2)
{
lresult.Message = "победил";
lresult.PlayerIndex = lMaxFaceIndex;
lresult.IsEnd = true;
return lresult;
}
int lPlaerCardsCount = mArr[lMaxFaceIndex].Count;
if (mUnknowTour > -1)
{
for (int i = mUnknowTour; i <= mCurrentTour; i++)
{
AddCardsToPlayer(i, lMaxFaceIndex);
}
mUnknowTour = -1;
}
else
{
AddCardsToPlayer(mCurrentTour, lMaxFaceIndex);
}
lresult.Message = "загреб краты";
for (int i = lPlaerCardsCount; i < mArr[lMaxFaceIndex].Count; i++)
{
lresult.Message += string.Format(" {0},", GetCardName(mArr[lMaxFaceIndex][i]));
}
lresult.PlayerIndex = lMaxFaceIndex;
mCurrentTour++;
return lresult;
}
Что не так в исходном коде
Приведённый метод заключает в себе знания о слишком многих вещах: как понять, какая ситуация сложилась в игре, и что делать в каждой ситуации. Все подробности свалены в одну кучу, и не сразу понятно, что к чему относится.
Положение усугубляются тем, что не все названия переменных дают чёткое представление об их смысле.
Также не в полной мере использованы возможности объектного программирования (например, метод получения названия карты не инкапсулирован в класс карты).
Как улучшить исходный код
Для рефакторинга длинных простыней кода я применяю «метод псевдокода». В общих чертах он заключается в том, что надо проанализировать существующий код и предельно кратко пересказать, что в этом коде происходит. Дальше этот пересказ нужно записать формально в виде псевдокода, а затем переписать один в один в виде кода на конечном языке. Самое важное в методе, чтобы в конечном коде не содержалось больше подробностей, чем есть в записи на псевдокоде.
После этого добавляются все недостающие подробности, но так, чтобы они размещались в отдельных методах и классах.
Кроме улучшения кода отдельной функции метод псевдокода может помочь улучшить и структуру классов.
Метод псевдокода на практике
Поскольку в основе алгоритма лежат правила карточной игры, то нет необходимости записывать псевдокод в точном соответствии с уже реализованным алгоритмом — вполне достаточно соответствия правилам.
снять на кон по одной карте каждого оставшегося игрока
находим всех игроков с самой старшей картой
если есть один игрок с самой старшей картой
то он забирает все карты с кона,
игроки без карты выбывают из игры,
если кон пустой, и есть только один игрок с картами
то игра закончена и он победитель
если есть несколько игроков с самой старшей картой
то продолжаем игру
Переписываю на конечный язык:
public TourResult MakeTour()
{
CardStake stake = activePlayers.DealCards();
Player[] greaterCardPlayers = stake.playersWithGreaterCard();
if (greaterCardPlayers.Count() == 1) {
greaterCardPlayers[0].getStake(stake);
activePlayers.ExcludeWithoutCards();
if (activePlayers.isLeftOne()) {
return new TourResultWin(activePlayers.LastPlayer());
}
return new TourResultCardsTaken(greaterCardPlayers[0], stake);
}
return new TourResultContinue();
}
Теперь алгоритм игры предельно понятен и доступен для внесения изменений. Чтобы спрятать все второстепенные знания, мне понадобились несколько дополнительных классов:
- «кон» для операций с картами на кону игры (поиск игроков с самой старшей картой)
- «карта на кону» — связывает конкретную карту с игроком, от которого она пришла
- «активные игроки» — для операций с игроками (сдать карты на кон; исключить из игры игроков без карт; проверить, что остался только один)
- иерархия классов «результат хода»: один родитель и три наследника по одному для каждого вида результата, каждый со своим набором полей
Не следует думать, что увеличившееся количество классов усложняет проект. Все эти сущности в любом случае присутствуют в предметной области, вопрос лишь в том, выразим ли мы их в коде явно как отдельные сущности со своими собственными свойствами и поведением, или же размажем эти свойства и поведение по коду каких-то других сущностей.
Моя личная практика программирования убедительно показала, что чем точнее множество классов проекта соответствует сущностям предметной области, тем проще такой проект писать, отлаживать, исправлять, улучшать и развивать.
Теория
- Принцип единственного знания
- Объектно-ориентированный анализ