Оцените качество кода онлайн банка с 3 функциями
Изучаю С++ 3 недели, хочу узнать как можно улучшить мой код. Можете, пожалуйста, оценить этот вариант? Он не докончен, но хочется узнать в каком направлении стоит двигаться. Приложение выполняет роль онлайн банка с 3 функциями (произвести оплату, трансфер на карту, отчёт).
#include <iostream>
using namespace std;
int communIndex = 0, mobIndex = 0, otherIndex = 0;
double balance = 100;
void OptionMessage();
int MakePayment(double arr1[], double arr2[], double arr3[], const int length);
int CommunalPayment(double arr[], const int length);
int MobilePayment(double arr[], const int length) { return 0; }
int OtherPayment(double arr[], const int length) { return 0; }
void GenerateReceipt(int serviceType, double sum);
int main()
{
const int size = 50;
double communalArr[size]{};
double mobileArr[size]{};
double otherArr[size]{};
enum mainOpt { EXIT, PAYMENT, TRANSFER, REPORT };
enum mobOpt{BAKCELL, AZERCELL};
enum otherOpt{DONATE};
int userChoice;
OptionMessage();
cin >> userChoice;
while (userChoice)
{
switch (userChoice)
{
case EXIT:
userChoice = 0;
break;
case PAYMENT:
return MakePayment(communalArr, mobileArr, otherArr, size);
break;
case TRANSFER:
int SummTransfer;
int CardNum;
cout << "Enter transfer card number: " << endl;
cin >> CardNum;
if (CardNum < 10000000000000000) cout << "Wrong card number lenght!"; //проверка длины! error
break;
case REPORT:
break;
default:
break;
}
}
}
void OptionMessage() {//старт меню
cout << "\n=============OPTIONS=============\n";
cout << "1.Send Money" << endl;
cout << "2.Transfer" << endl;
cout << "3. Checkout" << endl;
cout << "4. Logout" << endl;
cout << "Your choice: ";//выбор пользователя
}
int MakePayment(double commArr[], double mobArr[], double otherArr[], const int length) {
int paymentChoice;//если выбор оплата, то ->
enum paymentOpt { EXIT, COMMUNAL, MOBILE, OTHER };
cout << "\n=============OPTIONS=============\n"
<< "\t1 -> COMMUNAL\n"
<< "\t2 -> MOBILE\n"
<< "\t3 -> OTHER\n"
<< "\t0 -> EXIT\n\n";
cout << "Your choice: ";
cin >> paymentChoice;
switch (paymentChoice)
{
case EXIT:
return 0;
break;
case COMMUNAL:
return CommunalPayment(commArr, length);
break;
case MOBILE:
return MobilePayment(mobArr, length);
break;
case OTHER:
return OtherPayment(otherArr, length);
break;
default:
cout << "NO SUCH OPTION";
return 1;
break;
}
}
int CommunalPayment(double arr[], const int length) {
int comChoice;
int abonentCode = 0;
cout << "Enter abonent code" << endl;
cin >> abonentCode;
if (abonentCode < 10000000 && abonentCode >!9999999) { cout << "Wrong code lenght!"; }//проверка длины кода абонента
cout << "Enter summ: \n";
double sum;
cin >> sum;
if (sum < 1)cout << "Summ is too small!";
enum communOpt { GAS = 1, WATER };
cout << "\n=============OPTIONS=============\n"
<< "\t1 -> GAS\n"
<< "\t2 -> WATER\n"
<< "\t0 -> EXIT\n\n";
cout << "Your choice: ";
cin >> comChoice;
switch (comChoice)
{
case GAS:
cout << "ABONENT CODE: ";
cin >> abonentCode;
cout << "AMMOUNT: ";
cin >> sum;
if (abonentCode > 9999999 && abonentCode < 100000000 && sum <= balance) {
balance -= sum;
arr[communIndex] = sum;
communIndex++;
GenerateReceipt(0, sum);
return 1;
}
return 0;
break;
case WATER:
break;
default:
break;
}
}
void GenerateReceipt(int serviceType, double amount) {
enum serviceOpt { gas, water, azercell, bakcell, donate, transfer };
static int ID = 1;
cout << "RECEIPT #" << ID << endl
<< "DATE: " << "11/07/2022\n"
<< "TYPE: ";
switch (serviceType)
{
case gas:
cout << "GAS\n";
break;
case water:
cout << "WATER\n";
break;
case azercell:
cout << "AZERCELL\n";
break;
case bakcell:
cout << "BAKCELL\n";
break;
case donate:
cout << "DONATE\n";
break;
case transfer:
cout << "TRANSFER\n";
break;
default:
cout << "UNKNOWN\n";
break;
}
cout << "AMOUNT: " << amount << endl << endl;
}
Ответы (2 шт):
Немного пройдусь по вашей
void GenerateReceipt(int serviceType, double amount);
Смотрите — вы передаете int serviceType, а только потом определяете enum serviceOpt. Вы уверены, что где-то концепция не поменяется и не нарушится соответствие значений переданного параметра и перечислений? Может, имеет смысл перечисление тогда делать глобальным?
А если нет — какой в нем вообще смысл? Для вывода строки? (кстати, непонятен смысл и неизменного и статического ID. Вероятно, это счетчик, который должен при каждом вызове расти? Да и жестко прошитая дата вызывает сомнения...
Ну, с датой разберётесь сами, а все остальное легким движением руки превращается в
void GenerateReceipt(int serviceType, double amount)
{
const char* serviceName[] =
{
"GAS", "WATER", "AZERCELL",
"BAKCELL","DONATE","TRANSFER","UNKNOWN"
};
if (serviceType >= size(serviceName))
serviceType = size(serviceName) - 1;
static int ID = 1;
cout << "RECEIPT #" << ID++
<< "\nDATE: " << "11/07/2022"
<< "\nTYPE: " << serviceName[serviceType]
<< "\nAMOUNT: " << amount << "\n\n";
}
Если про ошибки - @Harry уже написал.
Также - для баланса лучше использовать целочисленную арифметику. Т.е. сумма на счете может быть только целая (например в копейках). И перевод можно сделать только в целых. Т.е. если у вас на счете 125,31 руб, значит у вас 12531 копеек. А для процентов существуют заранее определенные банковские правила округления.
Ещё ошибка - при выборе PAYMENT совершится платеж и main завершится. Здесь оператор return лишний.
case PAYMENT:
return MakePayment(communalArr, mobileArr, otherArr, size);
break;
А в остальном - никогда нет предела совершенству! :)
- Если вы ставите тег
c++, то наверное нужно использовать классы? А у вас программа на с. От с++ - только использование потоков ввода/вывода вместоprinf()/scanf(). У вас есть как минимум 1 сущность -счет клиента. А еще можно сделать сущностьбанк(илипроцессинг) - в нем будут методыпроизвести оплату,трансфер на карту,отчёт. Еслиотчет- это лог операций по счету, то его тоже можно сделать отдельной сущностью. - Использование идиомы RAII (https://habr.com/ru/sandbox/21603/). Все ресурсы инкапсулируются в классах. Как минимум счет клиента нужно инкапсулировать, чтобы к нему не было доступа помимо встроенных методов.
- Глобальные переменные - зло. Особенно когда баланс глобальная переменная, которую может менять кто угодно и как угодно из любого места программы. При следовании RAII глобальные переменные попадут внутрь класса.
- Написанные вами функции, если будут методами класса, будут обращаться к данным внутри класса, и в них не нужно будет передавать длинные списки параметров.
- Аналогично -
enum. У вас есть энумы, а вы по-прежнему передаете в функции в качестве параметров целые числа, а потом сравниваете с энумами. Передавайте сразу значения энума - это механизм контроля ошибок и улучшения читаемости кода. И не экономьте на имениenum. Для примераGenerateReceipt(int serviceType, double amount)меняете наGenerateReceipt(enum ServiceOption serviceType, double amount)- т.е. вы сразу передаете в функцию значение изenum ServiceOption- компилятор постарается вас уберечь от ошибки. Про генерацию строк изenum@Harry уже написал. - Зачем хранить в массивах суммы по отдельным коммунальным платежам
double communalArr[size]{}; double mobileArr[size]{}; double otherArr[size]{};? Скорее всего вам нужен 1 лог по аккаунту, в котором будет учтено на какую цель пошел платеж. - Желательно отделить функции интерфейса (которые выводят меню, запрашивают выбор пользователя) от банковских функций.
Ну и т.д.
#include <iostream>
#include <vector>
using namespace std;
enum TypeOfPayment { Income, Gas, Water, Azercell, Bakcell, Donate, Transfer, Exit};
const char* PaymentName[] =
{
"INCOME", "GAS", "WATER", "AZERCELL",
"BAKCELL","DONATE","TRANSFER","UNKNOWN"
};
enum AccountError { AllOK, NotEnoughFunds, Unknown }; // Ошибки при платежах
const char* AccountErrorName[] =
{
"Operation is OK!",
"Not enough funds on user account!",
"Unknown error!"
};
// --------------------------------------------------------------------------
struct AccountLogRecord
{
unsigned ID; // можно вообще не добавлять - считать ID номер записи в векторе
string RecDate; // или использовать std::chrono::year_month_day
TypeOfPayment PaymentType;
unsigned Amount;
// string Comment;
// string DetailsPayee; // реквизиты получателя платежа
AccountLogRecord();
};
AccountLogRecord::AccountLogRecord()
{
ID = 0;
RecDate = "11.07.2022";
PaymentType = TypeOfPayment::Income;
Amount = 0;
}
// вывод записи лога в поток
std::ostream& operator<< (std::ostream &out, const AccountLogRecord& record)
{
cout << "RECEIPT #" << record.ID
<< "\tDATE: " << record.RecDate
<< "\tTYPE: " << PaymentName[record.PaymentType]
<< "\tAMOUNT: " << record.Amount << "\n";
return cout;
}
// --------------------------------------------------------------------------
class UserAccount
{
private:
unsigned Balance;
std::vector<AccountLogRecord> Log;
public:
UserAccount() : Balance (0) {};
unsigned GetBalance(void) const { return Balance; }
AccountError Increase(unsigned amount)
{ // можно добавить проверку на переполнение
Balance+=amount;
return AccountError::AllOK;
}
AccountError Decrease(unsigned amount)
{
if( Balance < amount )
return AccountError::NotEnoughFunds;
Balance-=amount;
return AccountError::AllOK;
}
const AccountLogRecord& GetLastLogRecord() const { return Log.back(); }
const AccountLogRecord& GetLastLogRecord(size_t num) const { return Log.at(num); }
void PrintLog(void) const
{
for(auto &i : Log)
cout << i;
}
void AddToLog(AccountLogRecord&& rec)
{
rec.ID = Log.size();
Log.push_back(rec);
}
};
// --------------------------------------------------------------------------
class BankProcessing
{
private:
UserAccount Account;
unsigned GetAmount(void) const;
unsigned GetAbonentCode(void) const;
long GetCardNumber(void) const;
void OperationMessage(AccountError mess) const { cout << "Result of operation: " << AccountErrorName[mess] << "\n";}
//Функции интерфейса - выводят меню и возвращают тип платежа
TypeOfPayment ShowPaimentMenu(void) const;
TypeOfPayment ShowCommunalMenu(void) const;
TypeOfPayment ShowCheckoutMenu(void) const;
public:
unsigned Increase(unsigned amount) noexcept;
unsigned MakePayment(unsigned amount, TypeOfPayment pay) noexcept;
TypeOfPayment ShowMainMenu(void);
};
unsigned BankProcessing::GetAmount(void) const
{ // проверка корректности введения суммы
unsigned amount;
cout << "\nEnter amount: ";
while(true)
{
cin >> amount;
if (amount > 1 && amount < 100000)
return amount;
cout << "Error! Amount must be in range 1 < amount < 100000.\n Enter valid amount:";
}
}
unsigned BankProcessing::GetAbonentCode(void) const
{ // проверка корректности введения кода абонента
unsigned abonentCode = 0;
cout << "\nEnter abonent code from 7 digits: ";
while(true)
{
cin >> abonentCode;
if (abonentCode >=1000000 && abonentCode < 10000000)
return abonentCode;
cout << "Error! Wrong code lenght!.\n Enter valid abonent code from 7 digits:";
}
}
long BankProcessing::GetCardNumber(void) const
{ // проверка корректности введения номера карты
long CardNum;
cout << "Enter transfer card number from 16 digits: " << endl;
while(true)
{
cin >> CardNum;
if (CardNum > 1000000000000000 && CardNum < 10000000000000000) // > 1000'0000'0000'0000 < 1'0000'0000'0000'0000)
break;
cout << "Wrong card number lenght!\n Enter valid transfer card number from 16 digits: ";
}
return CardNum;
}
unsigned BankProcessing::Increase(unsigned amount) noexcept
{
AccountError Res = Account.Increase(amount);
AccountLogRecord tmp;
tmp.Amount = amount;
Account.AddToLog( std::move(tmp) );
OperationMessage( AccountError::AllOK );
cout << Account.GetLastLogRecord();
return Account.GetBalance();
}
unsigned BankProcessing::MakePayment(unsigned amount, TypeOfPayment pay) noexcept
{
AccountError Result = Account.Decrease(amount);
if(Result != AccountError::AllOK)
{
cout << "Error!\n";
OperationMessage( Result );
return Account.GetBalance();
}
AccountLogRecord tmp;
tmp.Amount = amount;
tmp.PaymentType = pay;
Account.AddToLog( std::move(tmp) );
OperationMessage( Result );
cout << Account.GetLastLogRecord();
return Account.GetBalance();
}
TypeOfPayment BankProcessing::ShowMainMenu(void)
{
enum MainMenuOption { EXIT, PAYMENT, TRANSFER, REPORT };
while(true)
{
cout << "\n\n\n=============OPTIONS=============\n"
<< "1. Send Money\n"
<< "2. Transfer\n"
<< "3. Checkout\n"
<< "0. Logout\n"
<< "Your choice: ";//выбор пользователя
int userChoice;
while (true)
{
cin >> userChoice;
if(userChoice >= MainMenuOption::EXIT && userChoice <= MainMenuOption::REPORT) // если ввод корректный - выход из цикла
break;
cout << "Wrong choice!\n Enter your choice again: ";
}
TypeOfPayment TypeP = TypeOfPayment::Exit;
switch (userChoice)
{
case MainMenuOption::EXIT : return TypeOfPayment::Exit; // выход из главного меню и программы
case MainMenuOption::PAYMENT : TypeP = ShowPaimentMenu(); break;
case MainMenuOption::TRANSFER : TypeP = TypeOfPayment::Transfer; break;
case MainMenuOption::REPORT : Account.PrintLog(); break;
}
if(TypeP == TypeOfPayment::Exit) // выход из подменю в главное меню
continue;
long CardNumber = 0;
// обработка платежа
switch(TypeP) // TypeOfPayment { Income, Gas, Water, Azercell, Bakcell, Donate, Transfer, Exit};
{
case TypeOfPayment::Gas :
case TypeOfPayment::Water : GetAbonentCode(); break;
case TypeOfPayment::Transfer : CardNumber = GetCardNumber(); break;
case TypeOfPayment::Azercell :
case TypeOfPayment::Bakcell :
case TypeOfPayment::Donate : continue; // заглушка - переход на следующую итерацию цикла
};
unsigned Amount = GetAmount();
MakePayment( Amount, TypeP);
}
}
// отображает меню и возвращает тип платежа
TypeOfPayment BankProcessing::ShowPaimentMenu(void) const
{
enum PaimentMenuOption { EXIT, COMMUNAL, MOBILE, OTHER };
TypeOfPayment TypeP = TypeOfPayment::Exit;
int paymentChoice;//если выбор оплата, то ->
cout << "\n=============OPTIONS=============\n"
<< "\t1 -> COMMUNAL\n"
<< "\t2 -> MOBILE\n"
<< "\t3 -> OTHER\n"
<< "\t0 -> EXIT\n"
<< "Your choice: ";
while (true)
{
cin >> paymentChoice;
if(paymentChoice >= PaimentMenuOption::EXIT && paymentChoice <= PaimentMenuOption::OTHER) // если ввод корректный - выход из цикла
break;
cout << "Wrong choice!\n Enter your choice again: ";
}
switch (paymentChoice)
{
case PaimentMenuOption::EXIT : return TypeOfPayment::Exit; // выход в главное меню
case PaimentMenuOption::COMMUNAL: return ShowCommunalMenu();
case PaimentMenuOption::MOBILE:
case PaimentMenuOption::OTHER: return TypeOfPayment::Exit;
};
return TypeOfPayment::Exit;
}
TypeOfPayment BankProcessing::ShowCommunalMenu(void) const
{
enum CommunalMenuOption { EXIT, GAS, WATER };
cout << "\n=============OPTIONS=============\n"
<< "\t1 -> GAS\n"
<< "\t2 -> WATER\n"
<< "\t0 -> EXIT\n\n";
int paymentChoice;
while (true)
{
cin >> paymentChoice;
if(paymentChoice >= CommunalMenuOption::EXIT && paymentChoice <= CommunalMenuOption::WATER) // если ввод корректный - выход из цикла
break;
cout << "Wrong choice!\n Enter your choice again: ";
}
switch (paymentChoice)
{
case CommunalMenuOption::EXIT : return TypeOfPayment::Exit;
case CommunalMenuOption::GAS : return TypeOfPayment::Gas;
case CommunalMenuOption::WATER : return TypeOfPayment::Water;
}
return TypeOfPayment::Exit;
}
int main()
{
BankProcessing BankProcess;
BankProcess.Increase(100);
BankProcess.ShowMainMenu();
}