Методические указания по выполнению лабораторных и практических работ по мдк
Скачать 3.25 Mb.
|
Задание: −Решите задачу оптимизации кода (файл с кодом возьмите у преподавателя), используя только элементарные конструкции (последовательность, ветвления, циклы). Программа должна быть рабочей. −Оптимизировать программу (можно использовать процедуры или функции). −Оптимизированная программа должна содержать проверки всех переменных, которые вводятся с клавиатуры. −Для созданных программ оценить метрические характеристики по Холстеду; −Сравнить полученные результаты. Оформить результаты в таблицу. Сделать соответствующие выводы. Расчет метрики Холстеда: Метрика Холстеда относится к метрикам, вычисляемым на основании анализа числа строк и синтаксических элементов исходного кода программы. Метрика Холстеда позволяет оценить размер (в словах) и объем в битах программы на стадии анализа требований. Используя нормы выработки операторов в день можно оценить время на разработку. Основу метрики Холстеда составляют четыре измеряемые характеристики программы: n1 — число уникальных операторов + 135 программы, включая символы-разделители, имена процедур и знаки операций (словарь операторов); n2 — число уникальных операндов программы (словарь операндов); N1 — общее число операторов в программе; N2 — общее число операндов в программе. +, *, /, - это операторы x, у, z, 999, -25, number1 - это операнды На основании этих характеристик рассчитываются оценки: Словарь программы (Halstead Program Vocabulary, HPVoc): n = n1 + n2; Длина программы (Halstead Program Length, HPLen): N = N1 + N2; Объем программы (Halstead Program Volume, HPVol): V = N log2 n; Сложность программы (Halstead Difficulty, HDiff): D = (n1/2) × (N2 / n2); На основе показателя HDiff предлагается оценивать усилия программиста при разработке при помощи показателя HEff (Halstead Effort): H = D × V. Практическая работа № 1.33. Рефакторинг кода Цель работы: изучить технологию рефакторинга программного кода Ход работы Задание: Задание 1: В рамках лабораторной работы приведите примеры реализации рефакторинга в следующих системах: −Visual Studio. −Visual Assist X. −Refactor. −JustCode −ReSharper −CodeIt Ход выполнения работы 1. Выполнить анализ программного кода разрабатываемого ПО и модульных тестов с целью выявления плохо организованного кода. 2. Используя шаблоны рефакторинга, выполнить реорганизацию программного кода разрабатываемого ПО. 3. Выполнить описание произведенных операций рефакторинга (было-стало-шаблон рефакторинга). 4. В случае необходимости скорректировать проектную документацию (диаграммы классов, последовательностей). 5. Сделать выводы по результатам выполнения работы. Класс Main Было: package game; import game.Characters.*; import game.Characters.Character; import game.Energetics.Energetic; import game.Energetics.Lightning; import game.Levels.Block; import game.Levels.Level; import game.Levels.Level_data; import game.Weapon.Bullet; import game.Weapon.Weapon; import javafx.animation.AnimationTimer; import javafx.application.Application; import javafx.scene.Scene; import javafx.scene.image.Image; 136 import javafx.scene.input.KeyCode; import javafx.scene.layout.Pane; import javafx.stage.Stage; import java.io.DataInputStream; import java.io.FileInputStream; import java.io.IOException; import java.util.ArrayList; import java.util.HashMap; public class Main extends Application { public static ArrayList @Override public void handle(long now) { update(); } }; private static void update() { for (EnemyBase enemy : enemies) { enemy.update(); if (enemy.getDelete()) { enemies.remove(enemy); break; } } Bullet.update(); Controller.update(); booker.update(); if (!energetic.getName().equals("")) energetic.update(); if (levelNumber > 0) elizabeth.update(); if (lightning != null) { lightning.update(); if (lightning.getDelete()) lightning = null; } 137 menu.update(); hud.update(); weapon.update(); if (booker.getTranslateX() > Level_data.BLOCK_SIZE * 295) cutScene = new CutScenes(levelNumber); } @Override public void start(Stage primaryStage) throws Exception { stage = primaryStage; scene = new Scene(appRoot, 1280, 720); appRoot.getChildren().add(gameRoot); level = new Level(); try (DataInputStream dataInputStream = new DataInputStream(new FileInputStream("C:/DeadShock/saves/data.dat"))) { levelNumber = dataInputStream.readInt(); level.createLevels(levelNumber); level.changeImageView(levelNumber); vendingMachine = new VendingMachine(); booker = new Character(); booker.setMoney(dataInputStream.readInt()); booker.setSalt(dataInputStream.readByte()); booker.setCountLives(2); weapon = new Weapon(); weapon.setWeaponClip(dataInputStream.readInt()); weapon.setBullets(dataInputStream.readInt()); hud = new HUD(); elizabeth = new Elizabeth(); energetic = new Energetic(); } catch (IOException e) { levelNumber = 0; level.createLevels(levelNumber); vendingMachine = new VendingMachine(); booker = new Character(); weapon = new Weapon(); hud = new HUD(); energetic = new Energetic(); tutorial = new Tutorial(levelNumber); } switch (levelNumber) { case 0: enemies.add(new EnemyComstock(Level_data.BLOCK_SIZE * 117, 200)); enemies.add(new EnemyComstock(Level_data.BLOCK_SIZE * 127, 200)); enemies.add(new EnemyComstock(Level_data.BLOCK_SIZE * 148, 200)); enemies.add(new EnemyComstock(Level_data.BLOCK_SIZE * 161, 200)); enemies.add(new EnemyComstock(Level_data.BLOCK_SIZE * 171, 200)); enemies.add(new EnemyComstock(Level_data.BLOCK_SIZE * 185, 200)); enemies.add(new EnemyComstock(Level_data.BLOCK_SIZE * 204, 200)); enemies.add(new EnemyComstock(Level_data.BLOCK_SIZE * 215, 200)); enemies.add(new EnemyComstock(Level_data.BLOCK_SIZE * 228, 200)); enemies.add(new EnemyComstock(Level_data.BLOCK_SIZE * 233, 200)); enemies.add(new EnemyComstock(Level_data.BLOCK_SIZE * 243, 200)); enemies.add(new EnemyComstock(Level_data.BLOCK_SIZE * 252, 200)); enemies.add(new EnemyComstock(Level_data.BLOCK_SIZE * 262, 200)); enemies.add(new EnemyComstock(Level_data.BLOCK_SIZE * 277, 200)); enemies.add(new EnemyComstock(Level_data.BLOCK_SIZE * 280, 200)); enemies.add(new EnemyComstock(Level_data.BLOCK_SIZE * 286, 200)); 138 break; case 1: enemies.add(new EnemyRedEye(Level_data.BLOCK_SIZE * 57, 200)); enemies.add(new EnemyRedEye(Level_data.BLOCK_SIZE * 67, 200)); enemies.add(new EnemyRedEye(Level_data.BLOCK_SIZE * 74, 200)); enemies.add(new EnemyComstock(Level_data.BLOCK_SIZE * 87, 200)); enemies.add(new EnemyRedEye(Level_data.BLOCK_SIZE * 104, 150)); enemies.add(new EnemyComstock(Level_data.BLOCK_SIZE * 117, 200)); enemies.add(new EnemyRedEye(Level_data.BLOCK_SIZE * 133, 200)); enemies.add(new EnemyRedEye(Level_data.BLOCK_SIZE * 156, 200)); enemies.add(new EnemyComstock(Level_data.BLOCK_SIZE * 177, 200)); enemies.add(new EnemyComstock(Level_data.BLOCK_SIZE * 193, 200)); enemies.add(new EnemyRedEye(Level_data.BLOCK_SIZE * 201, 200)); enemies.add(new EnemyRedEye(Level_data.BLOCK_SIZE * 216, 200)); enemies.add(new EnemyRedEye(Level_data.BLOCK_SIZE * 224, 200)); enemies.add(new EnemyComstock(Level_data.BLOCK_SIZE * 246, 200)); enemies.add(new EnemyRedEye(Level_data.BLOCK_SIZE * 260, 200)); enemies.add(new EnemyComstock(Level_data.BLOCK_SIZE * 277, 100)); Level_data.enemyBlocks.add(new Block("invisible", Level_data.BLOCK_SIZE * 34, Level_data.BLOCK_SIZE * 13)); Level_data.enemyBlocks.add(new Block("invisible", Level_data.BLOCK_SIZE * 36, Level_data.BLOCK_SIZE * 13)); Level_data.enemyBlocks.add(new Block("invisible", Level_data.BLOCK_SIZE * 60, Level_data.BLOCK_SIZE * 9)); Level_data.enemyBlocks.add(new Block("invisible", Level_data.BLOCK_SIZE * 61, Level_data.BLOCK_SIZE * 9)); Level_data.enemyBlocks.add(new Block("invisible", Level_data.BLOCK_SIZE * 106, Level_data.BLOCK_SIZE * 7)); Level_data.enemyBlocks.add(new Block("invisible", Level_data.BLOCK_SIZE * 107, Level_data.BLOCK_SIZE * 7)); Level_data.enemyBlocks.add(new Block("invisible", Level_data.BLOCK_SIZE * 168, Level_data.BLOCK_SIZE * 13)); Level_data.enemyBlocks.add(new Block("invisible", Level_data.BLOCK_SIZE * 170, Level_data.BLOCK_SIZE * 13)); Level_data.enemyBlocks.add(new Block("invisible", Level_data.BLOCK_SIZE * 196, Level_data.BLOCK_SIZE * 13)); Level_data.enemyBlocks.add(new Block("invisible", Level_data.BLOCK_SIZE * 197, Level_data.BLOCK_SIZE * 13)); Level_data.enemyBlocks.add(new Block("invisible", Level_data.BLOCK_SIZE * 232, Level_data.BLOCK_SIZE * 8)); Level_data.enemyBlocks.add(new Block("invisible", Level_data.BLOCK_SIZE * 233, Level_data.BLOCK_SIZE * 8)); break; } menu = new Menu(); appRoot.getChildren().add(menu.menuBox); booker.translateXProperty().addListener( ((observable, oldValue, newValue) -> { int offset = newValue.intValue(); if (offset > 600 && offset < gameRoot.getWidth() - 680) { gameRoot.setLayoutX( - (offset - 600) ); level.getBackground().setLayoutX((offset - 600) / 1.5); } if (offset <= 100) level.getBackground().setLayoutX(0); })); vendingMachine.createButtons(); 139 stage.getIcons().add(new Image("file:/C:/DeadShock/images/icon.jpg")); stage.setTitle("DeadShock"); stage.setResizable(false); stage.setWidth(scene.getWidth()); stage.setHeight(scene.getHeight()); stage.setScene(scene); stage.show(); timer.start(); } public static void main(String[] args) { launch(args); } } Стало: package game; import game.Characters.*; import game.Characters.Character; import game.Energetics.Energetic; import game.Energetics.Lightning; import game.Levels.Block; import game.Levels.Level; import game.Levels.Level_data; import game.Weapon.Bullet; import game.Weapon.Weapon; import javafx.animation.AnimationTimer; import javafx.application.Application; import javafx.scene.Scene; import javafx.scene.image.Image; import javafx.scene.input.KeyCode; import javafx.scene.layout.Pane; import javafx.stage.Stage; import java.io.DataInputStream; import java.io.FileInputStream; import java.io.IOException; import java.util.ArrayList; import java.util.HashMap; public class Main extends Application { public static ArrayList 140 public static int levelNumber; static Level level; public static AnimationTimer timer = new AnimationTimer() { @Override public void handle(long now) { update(); } }; private void initContent() { appRoot.getChildren().add(gameRoot); level = new Level(); try (DataInputStream dataInputStream = new DataInputStream(new FileInputStream("C:/DeadShock/saves/data.dat"))) { levelNumber = dataInputStream.readInt(); level.createLevels(levelNumber); level.changeImageView(levelNumber); vendingMachine = new VendingMachine(); booker = new Character(); booker.setMoney(dataInputStream.readInt()); booker.setSalt(dataInputStream.readByte()); booker.setCountLives(2); weapon = new Weapon(); weapon.setWeaponClip(dataInputStream.readInt()); weapon.setBullets(dataInputStream.readInt()); hud = new HUD(); elizabeth = new Elizabeth(); energetic = new Energetic(); } catch (IOException e) { levelNumber = 0; level.createLevels(levelNumber); vendingMachine = new VendingMachine(); booker = new Character(); weapon = new Weapon(); hud = new HUD(); energetic = new Energetic(); tutorial = new Tutorial(levelNumber); } createEnemies(); menu = new Menu(); appRoot.getChildren().add(menu.menuBox); booker.translateXProperty().addListener( ((observable, oldValue, newValue) -> { int offset = newValue.intValue(); if (offset > 600 && offset < gameRoot.getWidth() - 680) { gameRoot.setLayoutX( - (offset - 600) ); level.getBackground().setLayoutX((offset - 600) / 1.5); } if (offset <= 100) level.getBackground().setLayoutX(0); })); vendingMachine.createButtons(); } public static void createEnemies() { switch (levelNumber) { case 0: enemies.add(new EnemyComstock(Level_data.BLOCK_SIZE * 117, 200)); enemies.add(new EnemyComstock(Level_data.BLOCK_SIZE * 127, 200)); 141 enemies.add(new EnemyComstock(Level_data.BLOCK_SIZE * 148, 200)); enemies.add(new EnemyComstock(Level_data.BLOCK_SIZE * 161, 200)); enemies.add(new EnemyComstock(Level_data.BLOCK_SIZE * 171, 200)); enemies.add(new EnemyComstock(Level_data.BLOCK_SIZE * 185, 200)); enemies.add(new EnemyComstock(Level_data.BLOCK_SIZE * 204, 200)); enemies.add(new EnemyComstock(Level_data.BLOCK_SIZE * 215, 200)); enemies.add(new EnemyComstock(Level_data.BLOCK_SIZE * 228, 200)); enemies.add(new EnemyComstock(Level_data.BLOCK_SIZE * 233, 200)); enemies.add(new EnemyComstock(Level_data.BLOCK_SIZE * 243, 200)); enemies.add(new EnemyComstock(Level_data.BLOCK_SIZE * 252, 200)); enemies.add(new EnemyComstock(Level_data.BLOCK_SIZE * 262, 200)); enemies.add(new EnemyComstock(Level_data.BLOCK_SIZE * 277, 200)); enemies.add(new EnemyComstock(Level_data.BLOCK_SIZE * 280, 200)); enemies.add(new EnemyComstock(Level_data.BLOCK_SIZE * 286, 200)); break; case 1: enemies.add(new EnemyRedEye(Level_data.BLOCK_SIZE * 57, 200)); enemies.add(new EnemyRedEye(Level_data.BLOCK_SIZE * 67, 200)); enemies.add(new EnemyRedEye(Level_data.BLOCK_SIZE * 74, 200)); enemies.add(new EnemyComstock(Level_data.BLOCK_SIZE * 87, 200)); enemies.add(new EnemyRedEye(Level_data.BLOCK_SIZE * 104, 150)); enemies.add(new EnemyComstock(Level_data.BLOCK_SIZE * 117, 200)); enemies.add(new EnemyRedEye(Level_data.BLOCK_SIZE * 133, 200)); enemies.add(new EnemyRedEye(Level_data.BLOCK_SIZE * 156, 200)); enemies.add(new EnemyComstock(Level_data.BLOCK_SIZE * 177, 200)); enemies.add(new EnemyComstock(Level_data.BLOCK_SIZE * 193, 200)); enemies.add(new EnemyRedEye(Level_data.BLOCK_SIZE * 201, 200)); enemies.add(new EnemyRedEye(Level_data.BLOCK_SIZE * 216, 200)); enemies.add(new EnemyRedEye(Level_data.BLOCK_SIZE * 224, 200)); enemies.add(new EnemyComstock(Level_data.BLOCK_SIZE * 246, 200)); enemies.add(new EnemyRedEye(Level_data.BLOCK_SIZE * 260, 200)); enemies.add(new EnemyComstock(Level_data.BLOCK_SIZE * 277, 100)); Level_data.enemyBlocks.add(new Block("invisible", Level_data.BLOCK_SIZE * 34, Level_data.BLOCK_SIZE * 13)); Level_data.enemyBlocks.add(new Block("invisible", Level_data.BLOCK_SIZE * 36, Level_data.BLOCK_SIZE * 13)); Level_data.enemyBlocks.add(new Block("invisible", Level_data.BLOCK_SIZE * 60, Level_data.BLOCK_SIZE * 9)); Level_data.enemyBlocks.add(new Block("invisible", Level_data.BLOCK_SIZE * 61, Level_data.BLOCK_SIZE * 9)); Level_data.enemyBlocks.add(new Block("invisible", Level_data.BLOCK_SIZE * 106, Level_data.BLOCK_SIZE * 7)); Level_data.enemyBlocks.add(new Block("invisible", Level_data.BLOCK_SIZE * 107, Level_data.BLOCK_SIZE * 7)); Level_data.enemyBlocks.add(new Block("invisible", Level_data.BLOCK_SIZE * 168, Level_data.BLOCK_SIZE * 13)); Level_data.enemyBlocks.add(new Block("invisible", Level_data.BLOCK_SIZE * 170, Level_data.BLOCK_SIZE * 13)); Level_data.enemyBlocks.add(new Block("invisible", Level_data.BLOCK_SIZE * 196, Level_data.BLOCK_SIZE * 13)); Level_data.enemyBlocks.add(new Block("invisible", Level_data.BLOCK_SIZE * 197, Level_data.BLOCK_SIZE * 13)); Level_data.enemyBlocks.add(new Block("invisible", Level_data.BLOCK_SIZE * 232, Level_data.BLOCK_SIZE * 8)); +Level_data.enemyBlocks.add(new Block("invisible", Level_data.BLOCK_SIZE * 233, Level_data.BLOCK_SIZE * 8)); 142 break; } } private static void update() { for (EnemyBase enemy : enemies) { enemy.update(); Практическая работа № 1.34. Пользовательская и программная модели интерфейса Цель работы: закрепить теоретические знания по разработке пользовательского интерфейса; получить практические навыки по проектированию пользовательской и программной модели интерфейса. Теоретический материал Существуют три совершенно различные модели пользовательского интерфейса: модель рограммиста, модель пользователя и программная модель. Программист, разрабатывая пользовательский интерфейс, исходит из того, управление какими операциями ему необхо димо реализовать в пользовательском интерфейсе, и как это осуществить, не затрачивая ни существенных ресурсов компьютера, ни своих сил и времени. Его интересуют функциона льность, эффективность, технологичность, внутренняя стройность и другие не связанные с удобств ом пользователя характеристики программного обеспечения. Именно поэтому большинство интерфейсов существующих программ вызывают серьезные нарекания пользователей. С точки зрения здравого смысла хорошим следует считать интерфейс, при работе с которым пользователь получает именно то, что он ожидал. Представление пользователя о функция х интерфейса можно описать в виде пользовательской модели интерфейса. Пользовательская модель интерфейса - это совокупность обобщенных представлений конкретного пользователя или некоторой группы пользователей о процессах, происходящи х во время работы программы или программной системы. Эта модель базируется на особеннос тях опыта конкретных пользователей, который характеризуется: - уровнем подготовки в предметной области разрабатываемого программного обеспечения; - интуитивными моделями выполнения операций в этой предметной области; - уровнем подготовки в области владения компьютером; устоявшимися стереотипами работы с компьютером. Для построения пользовательской модели необходимо изучить перечисленные выше особенности опыта предполагаемых пользователей программного обеспечения. С этой цел ью используют опросы, тесты и даже фиксируют последовательность действий, осуществляем ых в процессе выполнения некоторых операций, на пленку. Приведение в соответствие моделей пользователя и программиста, а также построение на их базе программной модели (рис. 1.1) интерфейса задача не тривиальная. Причем, чем сложнее автоматизируемая предметная область, тем сложнее оказывается построить программную модель интерфейса, учитывающую особенности пользовательской модели и не требующую слишк ом больших затрат как в процессе разработки, так и во время работы. С этой точки зрения объектные интерфейсы кажутся наиболее перспективными, так как в их основе лежит именно отображение объектов предметной области, которыми оперируют пользователи. Хотя на настоящий мо мент времени их реализация достаточно трудоемка. 143 При создании программной модели интерфейса также следует иметь в виду, что из менять пользовательскую модель непросто. Повышение профессионального уровня пользователей и их подготовки в области владения компьютером в компетенцию разработчиков программного обеспечения не входит, хотя часто грамотно построенный интерфейс, который адекватно отображает сущность происходящих процессов, способствует росту квалификации пользователей. Интуитивные модели выполнения операций в предметной области должны стать основой для разработки интерфейса, а потому в большинстве случаев их необходимо не менять, а уточнять и совершенствовать. Именно нежелание или невозможность следования интуитивным моделя м выполнения операций приводит к созданию искусственных надуманных интерфейсов, кото рые негативно воспринимаются пользователями. Иногда кажется, что единственно доступный для изменения элемент - устоявшийся стереотип работы с компьютером. Однако ломка стереотипов - процедура болезненная. На это стоит решаться, если некоторое революционное изменение значительно расширяет возможности пользователя или облегчает его работу, например, переход к Windows-интерфейсам существенно упростил работу с компьютером огромному числу пользователей- непрофессионалов. Ломая же стереотипы по мелочам или неточно следуя принятой концепции, разработчик рискует оттолкнуть пользователей, которые просто не будут понимать, что происходит. В качестве примера можно вспомнить хотя бы путаницу с вызовом программ двойным щелчком правой клавиши мыши по пиктограмме рабочем столе или одинарным, если пиктограммы вынесена на панель Quick Launch (Быстрый Доступ) Windows. Критерии оценки интерфейса пользователем. Многочисленные опросы и обследован ия, проводимые ведущими фирмами по разработке программного обеспечения, показали, что основными критериями оценки интерфейсов пользователем являются: - простота освоения и запоминания операций системы - конкретно оценивают время освоения и продолжительность сохранения информации в памяти; - скорость достижения результатов при использовании системы - определяется количеством вводимых или выбираемых мышью команд и настроек; - субъективная удовлетворенность при эксплуатации системы удобство работы, утомляемость и т. д.). 144 Рис.1.1. Процесс проектирования пользовательского интерфейса Причем для пользователей - профессионалов, постоянно работающих с одним и тем же пакетом, на первое место достаточно быстро выходят второй и третий критерии, а для пользователей- непрофессионалов, работающих с программным обеспечением периодически и выполняющих сравнительно несложные задачи - первый и третий. С этой точки зрения на сегодняшний день наилучшими характеристиками для пользователей- профессионалов обладают интерфейсы со свободной навигацией, а для пользователейнепр офессионалов - интерфейсы прямого манипулирования. Давно замечено, что при выполнении операции копирования файлов при прочих равных условиях большинство профессионалов используют оболочки типа Far, а непрофессионалы - «перетаскивание объектов» Windows. Задания для выполнения №1 1. Разработать модель программиста, модель пользователя и программную модель для обучающе-контролирующей программы. Обучающе-контролирующая программа должна быть разработана в одной из сред ООП (Lazarus, Delphi, VB, С++ и т.д.) с испольованием всех, ранее изученных классов, компонентов и методов. Также программа должна включать теоретический, практический и контролирующий блоки. Модели представить в виде одного из вариантов: текстовое описание, схема, таблица. 145 2. Оформить работу в соответствии с требованиями ЕСПД (ГОСТ 19.101- 77, ГОСТ 19.102-77, ГОСТ 19.103-77, ГОСТ 19.104-78, ГОСТ 19.10578, ГОСТ 19.106- 78, ГОСТ 19.401-78, ГОСТ 19.604-78). При оформлении использовать MS Office или OpenOffice.org. 3. Сдать и защитить работу. Варианты заданий 1. Обучающе-контролирующая программа по Lazarus 2. Обучающе-контролирующая программа по Pascal 3. Обучающе-контролирующая программа по Word 4. Обучающе-контролирующая программа по Excel 5. Обучающе-контролирующая программа по Access 6. Обучающе-контролирующая программа по Power Point 7. Обучающе-контролирующая программа по Ramus 8. Обучающе-контролирующая программа по PhotoShop 9. Обучающе-контролирующая программа по теме «Компьютер и его ПО» 10. Обучающе-контролирующая программа по теме «История ВТ» 11. Обучающе-контролирующая программа по HTML Содержание и оформление отчета по лабораторной работе Отчѐт должен содержать: 1. Титульный лист. 2. Аннотацию. 3. Содержание. 4. Основную часть, оформленную в соответствии с требованиями ЕСПД (ГОСТ 19.101- 77, ГОСТ 19.102-77, ГОСТ 19.103-77, ГОСТ 19.104-78, ГОСТ 19.105-78, ГОСТ 19.106-78, ГОСТ 19.401-78, ГОСТ 19.604-78). 5. Заключение (описание результатов работы). Защита отчета по лабораторной работе заключается в предъявлении преподавателю полученных результатов (на экране монитора или у доски), демонстрации полученных навыков и ответах на вопросы преподавателя |