вторник, 28 июня 2011 г.

Разбор тестового задания на C#


На собеседование в одной из фирм мне дали вот такое тестовое задание на знание основ c#. Надеюсь, оно  и для вас не окажется сложным. Для тех кто хочет проверить себя, привожу решение этого задания.
 

Техническое задание

Требуется написать программу CalculateBonus для рассчета премии по следующей формуле:
Bonus = Salary * SalaryProcent/100% * Koef
где:
Salary - зарплата сотрудника , целое число типа int
SalaryProcent - процент от зарплаты, предназначенный на выплату премии, типа byte
Koef - поправочный коэффиециент , типа double
Параметры передаются из командной строки в следующем формате:
обязательный параметр:
argv[0] string (разделенные запятой код сотрудника(отдел и должность) и фамилия)
опциональные параметеры:
argv[1] int, зарплата, если неуказана принимается равной 500
argv[2] byte процент от зарплаты, предназначенный на выплату премии, по умолчанию 10%.
argv[3] double поправочный коэффициент, по умолчанию 0.96.
Например:
CalculateBonus.EXE 12,Сидоров 650 10 0.90 - означает запрос на рассчет
премии для сотрудника отдела с кодом 1 (QA) занимающего должность с кодом 2(Manager) с фамилией Сидоров, зарплата которого 650, процент от зарплаты на выплату премии составляет 10%, поправочный коэффициент = 0.90.
CalculateBonus.EXE 01,Петров - означает запрос на рассчет премии для сотрудинка отдела с кодом 0, состоящего в должности 1. Значения для остальных параметров следует взять по умолчанию.
C суммы премии большей чем 10, должен быть списан налог в размере 13%. Данная функциональность реализуется отдельной функцией bool PayTax(ref decimal bonus, out int TaxRate)
уменьшающей сумму премии(параметр Bonus) на сумму налогов , возравращающей в параметре TaxRate ставку налога, возвращаемое значение типа bool показывает были ли вычтены налоги.
Программа должна генерировать отчет в следующем формате:
Сотрудник: ИВАНОВ (Фамилия сотрудника в верхнем регистре)
Должность: Manager (Должность сотрудника)
Отдел: QA (Отдел сотрудника)
Премия: 55,42 (Вычисленная премия)
Включая налог: 13 % (В случае же если премия составила < 10 и налог невзимался "Не включая налог: 13%")
Программа должна возращать cледующие значения
0 - если премия успешно была начислена
-1 - если не был передан обязательный(0й) аргумент
-2 - если указаный департамент не существует
-3 - если указанная должность не существует
Все допустимые значения возврата должны быть членами переисления ReturnCodes

Особенности реализации:
Существуют следующие отделы
RD = 0, //Отдел разработок
QA, //Отдел тестирования
Support, //Отдел технической поддержки
Sales, //Отдел продаж
Marketing, //Отдел маркетинга
Все допустимые коды отделов должны быть членами перечисления Departments.
Существвуют следующие должности
Employee = 0,
Lead,
Manager,
Director
Все допустимые коды должностей должны быть членами перечисления Ranks
Переменная для хранения итоговой премии(Bonus) должна быть обьявлена как decimal , при вычислениях потеря точности должна быть сведена к возможному миниму.

Разбор задания

Итак, приступим. Опишем сначала в перечислениях существующие в программе должности, отделы и ошибки, возвращаемые при некорректной работе программы.
/// <summary>
/// Existing departments.
/// </summary>
public enum Departments
{
/// <summary>
/// Development Department.
/// </summary>
RD = 0, //Отдел разработок
/// <summary>
/// Testing Department.
/// </summary>
QA, //Отдел тестирования
/// <summary>
/// Technical Support Department.
/// </summary>
Support, //Отдел технической поддержки
/// <summary>
/// Sales Department.
/// </summary>
Sales, //Отдел продаж
/// <summary>
/// Marketing Department.
/// </summary>
Marketing, //Отдел маркетинга
}
/// <summary>
/// Existing ranks.
/// </summary>
public enum Ranks
{
/// <summary>
/// Employee.
/// </summary>
Employee,
/// <summary>
/// Lead.
/// </summary>
Lead,
/// <summary>
/// Manager.
/// </summary>
Manager,
/// <summary>
/// Director.
/// </summary>
Director
}
/// <summary>
/// Codes, which can returns the program.
/// </summary>
public enum ReturnCodes
{
/// <summary>
/// Successfully.
/// </summary>
Ok = 0,
/// <summary>
/// Incorrectly arguments command line.
/// </summary>
ErrorArgs = -1,
/// <summary>
/// Id departments is not validate.
/// </summary>
ErrorDepartments = -2,
/// <summary>
/// Id ranks is not validate.
/// </summary>
ErrorRanks = -3
}
Далее опишем самый главный класс Employee, в котором содержатся соответствующие свойства типа lastName, department и д.р., а также метод для вычисления бонуса CulcBonus()
/// <summary>
/// Class provides the employee.
/// </summary>
public class Employee
{
/// <summary>
/// The last name of employee.
/// </summary>
private string lastName;
/// <summary>
/// The reedonly properties of field lastName.
/// </summary>
public string LastName
{
get { return lastName; }
}
/// <summary>
/// The department of employee.
/// </summary>
private Departments department;
/// <summary>
/// The reedonly properties of field department.
/// </summary>
public Departments Department
{
get { return department; }
}
/// <summary>
/// The role of employee.
/// </summary>
private Ranks role;
/// <summary>
/// The reedonly properties of field role.
/// </summary>
public Ranks Role
{
get { return role; }
}
/// <summary>
/// The bonus of employee.
/// </summary>
private decimal bonus;
/// <summary>
/// The properties of field bonus.
/// </summary>
public decimal Bonus
{
get { return bonus; }
set { bonus = value; }
}
/// <summary>
/// The salary of employee.
/// </summary>
private int salary;
/// <summary>
/// Percentage of salary, which accrue bonus.
/// </summary>
private byte percentOfSalary;
/// <summary>
/// Correction factor of bonus.
/// </summary>
private double correctionFactor;
/// <summary>
/// Constructor.
/// </summary>
/// <param name="department">ID departments.</param>
/// <param name="role">ID ranks.</param>
/// <param name="lastName">Last name.</param>
/// <param name="salary">Salary.</param>
/// <param name="percentOfSalary">% of salary to bonus.</param>
/// <param name="correctionFactor">Correction factor.</param>
public Employee(Departments department, Ranks role, string lastName, int salary, byte percentOfSalary, double correctionFactor)
{
this.department = department;
this.role = role;
this.lastName = lastName;
this.salary = salary;
this.percentOfSalary = percentOfSalary;
this.correctionFactor = correctionFactor;
}
/// <summary>
/// Constructor.
/// </summary>
/// <param name="department">ID departments.</param>
/// <param name="role">ID ranks.</param>
/// <param name="lastName">Last name.</param>
/// <param name="salary">Salary.</param>
/// <param name="percentOfSalary">% of salary to bonus.</param>
public Employee(Departments department, Ranks role, string lastName, int salary, byte percentOfSalary)
{
this.department = department;
this.role = role;
this.lastName = lastName;
this.salary = salary;
this.percentOfSalary = percentOfSalary;
this.correctionFactor = 0.96;
}
/// <summary>
/// Constructor.
/// </summary>
/// <param name="department">ID departments.</param>
/// <param name="role">ID ranks.</param>
/// <param name="lastName">Last name.</param>
/// <param name="salary">Salary.</param>
public Employee(Departments department, Ranks role, string lastName, int salary)
{
this.department = department;
this.role = role;
this.lastName = lastName;
this.salary = salary;
this.percentOfSalary = 10;
this.correctionFactor = 0.96;
}
/// <summary>
/// Constructor.
/// </summary>
/// <param name="department">ID departments.</param>
/// <param name="role">ID ranks.</param>
/// <param name="lastName">Last name.</param>
public Employee(Departments department, Ranks role, string lastName)
{
this.department = department;
this.role = role;
this.lastName = lastName;
this.salary = 500;
this.percentOfSalary = 10;
this.correctionFactor = 0.96;
}
/// <summary>
/// Calculating bonus.
/// </summary>
public void CulcBonus()
{
bonus = (decimal)((salary * percentOfSalary / 100) * correctionFactor);
}
}
Затем в теле программы, определяем константы приложения, считываем аргументы командной строки , рассчитываем бонус работника и выводим результат на экран.
/// <summary>
/// The Main class which contains the entry point.
/// </summary>
public class MainClass
{
/// <summary>
/// The minimum bonus for the tax.
/// </summary>
public const int minBonusOfTax = 10;
/// <summary>
/// Tax Rate.
/// </summary>
public const int constTaxRate = 13;
/// <summary>
/// Entry point.
/// </summary>
/// <param name="args">Arguments command line.
/// Command-line arguments to be set as follows:AB,C D E F
/// where A - ID departments
/// where В - ID ranks
/// where С - last name
/// where D - salary
/// where E - % of salary to bonus
/// where D - correction factor</param>
/// <returns></returns>
static public int Main(string[] args)
{
Employee employee = null;
int role = 0;
int department = 0;
string[] strs = null;
//Разбиваем первый аргумент на составляющие.
try
{
strs = args[0].Split(',');
if (strs.Length != 2)
{
Console.WriteLine("Входные параметры заданы неверно!");
return (int)ReturnCodes.ErrorArgs;
}
department = Int32.Parse(strs[0].Substring(0, 1));
role = Int32.Parse(strs[0].Substring(1, 1));
}
#region CatchBlock
catch (IndexOutOfRangeException exIndex)
{
Console.WriteLine("Не заданы параметры командной строки!");
return (int)ReturnCodes.ErrorArgs;
}
catch (Exception ex)
{
Console.WriteLine("Не верный формат входных параметров!");
Console.WriteLine("Формат должен быть следующим: AB,C D E F ");
Console.WriteLine("где A - ID отдела(число) ");
Console.WriteLine("где В - ID должности(число) ");
Console.WriteLine("где С - фамилия работника(строка) ");
Console.WriteLine("где D - зарплата сотрудника(число) ");
Console.WriteLine("где E - % от зарплаты на премию(число) ");
Console.WriteLine("где D - поправочный коэффициент(число) ");
return (int)ReturnCodes.ErrorArgs;
}
#endregion
int check = 0;
//Проверяем на существование введённых номера отдела и должности
check = CheckExistence(role, department);
if (check != 0)
{
return check;
}
//В зависимости от количества аргументов командной строки, создаём объект Emploee
switch (args.Length)
{
case 1:
{
employee = new Employee((Departments)department, (Ranks)role, strs[1]);
break;
}
case 2:
{
try
{
employee = new Employee((Departments)department, (Ranks)role, strs[1], Int32.Parse(args[1]));
}
#region CatchBlock
catch (FormatException e)
{
Console.WriteLine("Неверный формат данных у зарплаты!");
Console.WriteLine("Зарплата должна указываться числом!");
return (int)ReturnCodes.ErrorArgs;
}
#endregion
break;
}
case 3:
{
try
{
employee = new Employee((Departments)department, (Ranks)role, strs[1], Int32.Parse(args[1]), Byte.Parse(args[2]));
}
#region CatchBlock
catch (FormatException e)
{
Console.WriteLine("Неверный формат данных у зарплаты или у процентов бонуса!");
Console.WriteLine("Зарплата и проценты должны указываться числами!");
return (int)ReturnCodes.ErrorArgs;
}
#endregion
break;
}
case 4:
{
try
{
employee = new Employee((Departments)department, (Ranks)role, strs[1], Int32.Parse(args[1]), Byte.Parse(args[2]), Double.Parse(args[3]));
}
#region CatchBlock
catch (FormatException e)
{
Console.WriteLine("Неверный формат данных у зарплаты или у процентов или у коэффициента!");
Console.WriteLine("Зарплата, проценты и коэффициент должны указываться числами!");
return (int)ReturnCodes.ErrorArgs;
}
#endregion
break;
}
default:
{
Console.WriteLine("ERROR: Не указан обязательный аргумент командной строки!");
return (int)ReturnCodes.ErrorArgs;
}
}
try
{
//рассчитываем бонус работника
employee.CulcBonus();
}
#region CatchBlock
catch (NullReferenceException exNull)
{
Console.WriteLine("Невозможно рассчитать бонус рабочего, т.к. входящие параметры заданы не верно!");
return (int)ReturnCodes.ErrorArgs;
}
#endregion
//Создаём вспомагательные переменные, т.к. св-ва нельзя передавать с ref/out.
decimal bonus = employee.Bonus;
int TaxRate=0;
//Снимается налог с бонуса
bool payTax=PayTax(ref bonus, out TaxRate);
employee.Bonus = bonus;
//Вывод результатов на экран
Console.WriteLine("Cотрудник:"+employee.LastName.ToUpper());
Console.WriteLine("Должность:"+employee.Role.ToString());
Console.WriteLine("Отдел:" + employee.Department.ToString());
Console.WriteLine("Премия:" + employee.Bonus);
if (payTax == true)
{
Console.WriteLine("Включая налог:" + constTaxRate + "%");
}
else
{
Console.WriteLine("Не включая налог:"+ constTaxRate + "%");
}
return 0;
}
/// <summary>
/// The method for paying taxes.
/// </summary>
/// <param name="bonus">The amount of bonus.</param>
/// <param name="TaxRate">c</param>
/// <returns>True - if tax paid, false - if tax is not paid.</returns>
static bool PayTax(ref decimal bonus, out int TaxRate)
{
if (bonus > minBonusOfTax)
{
TaxRate = constTaxRate;
bonus = bonus - (bonus * TaxRate / 100);
return true;
}
else
{
TaxRate = 0;
return false;
}
}
/// <summary>
/// The function validate id deparnments and id ranks.
/// </summary>
/// <param name="checkR">The id ranks.</param>
/// <param name="checkD">The id deparnments.</param>
/// <returns>0 - if successfully
/// -2 - if id departments is not validate
/// -3 - if id ranks is not validate</returns>
static int CheckExistence( int checkR, int checkD)
{
Array arrD = Enum.GetValues(typeof(Departments));
if (checkD > (int)arrD.GetValue(0) && checkD <= (int)arrD.GetValue(arrD.Length - 1))
{
Array arrR = Enum.GetValues(typeof(Ranks));
if (checkR > (int)arrR.GetValue(0) && checkR <= (int)arrR.GetValue(arrR.Length - 1))
return (int)ReturnCodes.Ok;
else
{
Console.WriteLine("Указанной должности не существует!");
return (int)ReturnCodes.ErrorDepartments;
}
}
else
{
Console.WriteLine("Указанного отдела не существует!");
return (int)ReturnCodes.ErrorRanks;
}
}
}
Надеюсь, вам понравилось задание, интересно будет увидеть ваши реализации.

7 комментариев:

  1. "...C суммы премии большей чем 10..."
    наверное правильно так: "... С суммы большей или равной 10...", потому что ниже идет "В случае же если премия составила < 10 и налог не взимался "Не включая налог: 13%"

    ОтветитьУдалить
  2. Не нашел в задании о том какой код должна вернуть программа, если формат аргументов неверный. Например в первом аргументе не запятая, а точка или вообще одно слово, а также если аргументы-цифры не могут быть "отпарсены". В общем, отойду немного от задания и создам еще один возвращаемый код.

    ОтветитьУдалить
  3. bonus = (decimal)((salary * percentOfSalary / 100) * correctionFactor); - есть риск, что будет переполнение при вычислении выражения "salary * percentOfSalary", int * byte -> int, если исходный int был большим, то в итоговый произведение не поместится.

    ОтветитьУдалить
  4. Вот моя версия, когда делал авторскую не смотрел, только потом сравнил, где-то подход совпадал, где-то нет.

    // Перечисление "Возвращаемые коды"
    public enum ReturnCodes
    {
    OK = 0,
    MissedArgument = -1,
    CantFindDept = -2,
    CantFindRank = -3,
    InvalidArgument = -4
    }

    // Перечисление "Отделы"
    public enum Departments
    {
    RD = 0, //Отдел разработок
    QA, //Отдел тестирования
    Support, //Отдел технической поддержки
    Sales, //Отдел продаж
    Marketing, //Отдел маркетинга
    }

    // Перечисление "Должности"
    public enum Ranks
    {
    Employee = 0,
    Lead,
    Manager,
    Director
    }

    ОтветитьУдалить
  5. // Класс "Сотрудник"
    public class Employee
    {
    // Свойство "Имя сотрудника"
    public string Name { get; set; }
    // Свойство "Должность"
    public Ranks Rank { get; set; }
    // Свойство "Отдел"
    public Departments Department { get; set; }

    // Свойство "Зарплата"
    public int Salary { get; set; }
    // Свойство "Процент премии"
    public byte SalaryPercent { get; set; }
    // Свойство "Поправочный коэффициент"
    public double Coefficient { get; set; }
    // Свойство "Бонус"
    public decimal Bonus
    {
    // Значения свойства вычисляется по формуле
    get
    {
    return decimal.Round((decimal)Salary * (decimal)SalaryPercent * (decimal)Coefficient / 100, 2);
    }

    // Значение свойства нельзя установить напрямую
    protected set
    {

    }
    }

    // Конструктор
    public Employee()
    {
    // Значения по умолчанию
    Salary = 500;
    SalaryPercent = 10;
    Coefficient = 0.96;
    }

    // Процедура инициализации свойств объекта из массива строк
    public ReturnCodes Parse(string[] args)
    {
    // Количество аргументов (по заданию не превышает четырех)
    byte argsCount = unchecked((byte)args.Count());

    // Первый аргумент обязательный
    if (argsCount < 1) return ReturnCodes.MissedArgument;

    // В первом аргументе должно быть не менее 4 символов (1-Отдел, 2-Должность, 3-Запятая, 4-Имя)
    if (args[0].Length < 4) return ReturnCodes.InvalidArgument;

    // Проверка на запятую
    if (args[0].Substring(2, 1) != ",") return ReturnCodes.InvalidArgument;

    // Получение отдела
    int department;
    if (!int.TryParse(args[0].Substring(0, 1), out department)) return ReturnCodes.InvalidArgument;
    if (!Enum.IsDefined(typeof(Departments), department)) return ReturnCodes.CantFindDept;
    this.Department = (Departments)department;

    // Получение должности
    int rank;
    if (!int.TryParse(args[0].Substring(1, 1), out rank)) return ReturnCodes.InvalidArgument;
    if (!Enum.IsDefined(typeof(Ranks), rank)) return ReturnCodes.CantFindRank;
    this.Rank = (Ranks)rank;

    // Получение имени
    this.Name = args[0].Substring(3);

    // Получение з/п
    if (argsCount > 1)
    {
    int salary;
    if (!int.TryParse(args[1], out salary)) return ReturnCodes.InvalidArgument;
    this.Salary = salary;
    }

    // Получение %
    if (argsCount > 2)
    {
    byte salaryPercent;
    if (!byte.TryParse(args[2], out salaryPercent)) return ReturnCodes.InvalidArgument;
    this.SalaryPercent = salaryPercent;
    }

    // Получение поправочного коэффициента
    if (argsCount > 3)
    {
    double coefficient;
    if (!double.TryParse(args[3], out coefficient)) return ReturnCodes.InvalidArgument;
    this.Coefficient = coefficient;
    }

    return ReturnCodes.OK;
    }

    // Процедура вывода информации о сотруднике
    public override string ToString()
    {
    return "Сотрудник: " + Name.ToUpper()
    + "\nДолжность: " + Rank
    + "\nОтдел: " + Department
    + "\nПремия: " + Bonus;
    }
    }

    ОтветитьУдалить
  6. // Класс "Получатель налогов"
    public static class TaxGetter
    {
    // Константа "Налоговая ставка"
    const int taxrate = 13;
    // Константа "Порог налогообложения"
    const int maxvalue = 10;

    // Функция, которая снимает % налога, если премия больше или равна порогу налогообложения
    public static bool PayTax(ref decimal bonus, out int TaxRate)
    {
    TaxRate = taxrate;

    if (bonus < maxvalue) return false;

    bonus = decimal.Round(bonus * (100 - taxrate)/100, 2);

    return true;
    }
    }

    ОтветитьУдалить
  7. // Основная программа
    class Program
    {
    static int Main(string[] args)
    {
    Employee employee = new Employee();
    ReturnCodes returnCode = employee.Parse(args);

    if (returnCode == ReturnCodes.OK)
    {
    Console.WriteLine(employee);

    decimal bonus = employee.Bonus;
    int TaxRate = 0;

    if (TaxGetter.PayTax(ref bonus, out TaxRate))
    Console.WriteLine("Включая налог: 13 %");
    else
    Console.WriteLine("Не включая налог: 13 %");
    }
    else
    {
    Console.WriteLine("Программа завершилась с ошибкой. Код ошибки: {0}", (int)returnCode);
    }

    Console.ReadKey();

    return (int)returnCode;
    }
    }

    ОтветитьУдалить