Сдача лабораторных работ включает два этапа: выполнение и защита.
- Выполнение подразумевает выполнение выданных преподавателем заданий на лабораторную работу с последующей подачей результатов на проверку. Результаты подаются путем создания Pull Request в этом репозитории (правила оформления указаны в соответствующем пункте), а проверка происходит заочно по установленному графику без участия студента. По результатам проверки, преподаватель в комментарии к Pull Request указывает на выявленные недостатки в решении (если такие имеются) и предоставляет студенту возможность исправить их и подать к перепроверке. После перепроверки, преподаватель оставляет финальный комментарий к итоговому решению, где указывает степень успешности выполнения поставленного задания и оценивает решение.
- Защита подразумевает демонстрацию работоспособности и корректности полученных результатов "вживую" во время лабораторных занятий, с дачей ответов на контрольные вопросы преподавателя.
Рейтинговая система оценивания (РСО): Символ
Баллов
Описание
Q R
60
рейтинговый балл
Q E
40
баллов за экзамен
Q B
10
бонусных баллов
Рейтинговый балл (Q R), в свою очередь, состоит из Символ
Баллов
Описание
Q Rlab
40
баллов за выполнение лабораторных
Q Rtst
5 + 5
баллов за выполнение двух МКР
Q Ratt
10
баллов за посещение занятий (лекций и лабораторных)
Условием допуска к экзамену является:
- Все лабораторные работы выполнены и защищены;
- Написаны все модульные контрольные работы;
- Суммарный набранный рейтинг составляет не менее 40 баллов.
Условие упрощенной процедуры сдачи экзамена (также известно как "автомат"):
- Все лабораторные работы выполнены и защищены;
- Написаны все модульные контрольные работы;
- Суммарный набранный рейтинг составляет не менее 40 баллов;
- Студент имеет не более 3х пропущенных занятий;
- Суммарный балл за модульные контрольные работы составляет не менее 40% от максимального.
Упрощенная процедура сдачи экзамена подразумевает рассчет баллов за экзамен как Q E = Q Rlab (баллы за экзамен приравниваются к баллам за лабораторные). Студент может отказаться от упрощенной процедуры по собственному желанию.
Выполнение и защита лабораторных работ оцениваются отдельно, по шкале от 0 до 5 баллов;
Итоговая оценка за лабораторную работу определяется по формуле:
где R LAB — итоговый рейтинговый балл за лабораторную работу; K LAB — коэффициент пропорциональности (фиксированный, в соответствии с РСО); N f — оценка за выполнение работы, от 0 до 5; N s — оценка за защиту лабораторной работы, от 0 до 5.
Таким образом, именно оценка за выполнение вносит наибольший вклад в итоговый балл
Максимальная оценка за выполнение лабораторной работы составляет:
- 5 баллов — если лабораторная работа выполнена и подана к финальной проверке без отклонений от графика проверки;
- 4 балла — если лабораторная работа выполнена и подана к финальной проверке с отклонением от графика проверки на одну неделю;
- 3 балла — если лабораторная работа выполнена и подана к финальной проверке с отклонением от графика проверки более, чем на одну неделю.
Максимальная оценка за защиту лабораторной работы составляет:
- 5 баллов — если лабораторная работа защищена вовремя, а также во время защиты студент демонстрирует глубокое владение теоретическим материалом, высокий уровень практических навыков, творческий подход к решению задания лабораторной работы, правильно и четко отвечает на вопросы преподавателя;
- 4 балла — если лабораторная работа защищена с опозданием на одну неделю, а также во время защиты студент демонстрирует глубокое владение теоретическим материалом, высокий уровень практических навыков, правильно и четко отвечает на вопросы преподавателя;
- 3 балла — если лабораторная работа защищена вовремя, во время защиты студент демонстрирует владение теоретическим материалом и практическими навыками на уровне, достаточном для выполнения задания лабораторной работы, отвечает на вопросы преподавателя с незначительными неточностями или лабораторная работа защищена с опозданием более, чем на одну неделю;
- 1.5 балла — во время защиты студент демонстрирует правильность выполнения заданий лабораторной работы, но тем не менее, не может правильно ответить на вопросы преподавателя;
- 0 баллов — если в процессе защиты лабораторной работы студент продемонстрировал вопиющее невладение материалом и выразил нежелание пытаться пересдавать ее в дальнейшем (например, после более тщательной теоретической подготовки к ответам на вопросы).
Поданные к проверке лабораторные работы проверяются в соответствии с графиком:
Каждая суббота, до 16:00 по киевскому времени |
Каждый понедельник, до 17:00 по киевскому времени |
При наличии уважительной причины — в другое удобное время, по предварительному согласованию с преподавателем не позже, чем за неделю |
Поданные Pull Request также могут проверяться непосредственно на лабораторном занятии. Стоит учитывать, что приоритет предоставляется студентам, защищающим уже выполненные и проверенные работы. Проверка Pull Request кропотлива и занимает порядка 10..30 минут.
Поданные вне приведенного в таблице выше графика Pull Request, на проверку которых на занятии времени не хватило поданными вовремя не считаются, поэтому настоятельно не рекомендуется затягивать до последнего.
На основании вышеприведенных положений, можно привести рекомендуемую последовательность действий:
- Задание к лабораторной работе выполняется в удобное для студента время и Pull Request подается к первой проверке до субботы 16:00;
- Преподаватель проверяет Pull Request, и при наличии недостатков в решении, студент имеет возможность исправить их до следующей проверки в понедельник 17:00;
- На следующем лабораторном занятии студент защищает выполненную и проверенную работу, и приступает к выполнению следующей. При наличии вопросов — их будет очень удобно сразу же задать преподавателю и получить ответы и рекомендации.
И так далее для всех последующих работ
Курс предусматривает наличие консультаций. Консультации проводятся в онлайн-режиме и использованием мессенджеров Telegram и Skype (или в оффлайн-режиме, по предварительному согласованию времени проведения с преподавателем).
Рекомендуется задавать вопросы в Telegram-группу курса. Задавать вопросы в виде личных сообщений в Telegram преподавателю (@thodnev) не рекомендуется, поскольку это препятствует расспостранению полезной информации среди студентов.
Преподаватель оставляет за собой право перенаправлять личные сообщения в Telegram, в которых не указана просьба о конфиденциальности в общую группу с целью обеспечения максимального охвата аудитории.
Предварительно необходимо ознакомиться с форматированием Telegram. Вопросы, содержащие фрагменты кода в виде изображений или не моноширинного текста будут немедленно удаляться без объяснения причин.
Преподаватель оставляет за собой право не отвечать на вопросы в Telegram в нерабочее время. Время, уделяемое ответам на вопросы в Telegram составляет не менее 1.5 часа в неделю в среднем.
По предварительному согласованию личным сообщением в Telegram, возможно организовать Skype-сессию видеосвязи — уместно в случае вопросов, требующих демонстрации экрана и подобных вопросов, для ответа на которые средств Teleram недостаточно.
normal text |
normal text |
**bold text** |
bold text |
__italic text__ |
italic text |
~~strikethrough text~~ |
|
some `inline code` part |
some inline code part |
```multi
line
code
```
|
multi
line
code
|
Ниже приведено краткое резюме правил оформления кода. Более подробно все описано в официальном Coding Style ядра, которым будем руководствоваться. В случае противоречий, приоритет имеют приведенные ниже правила. Список правил будет пополняться по мере необходимости.
Максимальная длина строки равна:
- 80 символов для модулей ядра и всего, что относится к дереву исходников ядра
- 99 символов для всего, что не относится к ядру
Пустые строки должны быть пустыми и не содержать других символов кроме
\n
. Все строки заканчиваются на\n
. Каждый исходник обязательно заканчивается пустой строкойОтступы:
- Для всего, что относится к ядру, в коде для отступа используется символ TAB. 1 отступ = 1 TAB. 1 TAB эквивалентен 8 пробелам, что необходимо выставить в редакторе
- Для всего, что не относится к ядру, для отступов используются символы пробела. 1 отступ = 1 TAB = 4 пробела
Фигурные скобки { }:
В определениях функций открывающая скобка ставится со следующей строки
inline unsigned int invert(unsigned int val) { return ~val; }
В случае с
if
-else
, используется следующий стиль:if (a == b) { do_first(); } else if (c == a) { do_next(); } else { do_nothing(); }
В случае с
switch
,case
должны размещаться на том же уровне идентации, что иswitch
. Если используются сквозные(не содержащиеbreak
, кромеdefault
)case
, они должны быть обозначены как fall-through. Не рекомендуется злоупотреблять использованием сквозныхcase
. Пример стиля:switch (state) { case STATE_INIT: do_first(); break; case STATE_RUN: do_run(); /* fall through */ case STATE_NEXT: do_next() break; default: return EUNKNOWN; }
Во всех остальных случаях, открывающая скобка ставится через пробел в той же строке, а закрывающая – на уровне идентации блока открывающей. Пример:
if (a == b) { for (int i = 0; i < sz; i++) { do_smthng(); } a = sz; }
Круглые скобки ( ):
В выражениях (statements), отделяются пробелами. Пример:
if (state) { ... } for (i = k; i >= 0; i--) { ... } while (!ret) { ... } do { ... } while (i < cnt);
В определении функций и их вызовах пробелами не отделяются:
bool is_last(struct item *it) { ... } bool tst = is_last(item_ptr); while (!is_last(ptr)) { ... }
Компаундные конструкции переносятся следующим образом:
В выражениях с
if
логический оператор переносится на следующую строку, которая начинается с двойной идентации. Пример:if ((LAST_ITEM == a) && (b != a) && (NOT_FIRST == c) && (k == p) && (NOT_FIRST != k) && (g == r) && ((a == r) || (b == r)) { do_something(); }
Таким образом, при чтении кода не нужно искать оператор на предыдущей строке, а за счет идентации часть тестируемого выражения не перепутать с выражениями внутри
if
-блока.В длинных вызовах функций, при переносе аргументы находятся на уровне первого символа за открывающей скобкой. Закрывающая скобка ставится за последним аргументом. Если это невозможно, первый аргумент ставится на уровне идентации плюс один от уровня идентации вызова. Пример:
// first variant - aligned with opening delimiter prn("Here we have some long call" "\nThis string literal is concatenated." "\nFinally we have params: %d, %d, %d\n", some_really_really_really_long_long_param, short_param_a, short_param_b); // second variant - add an extra level of indentation to distinguish arguments some_very_very_very_long_function_call( one_indented_arg, second_indented_arg);
Бинарные и тернарные операторы (
+
,-
,*
,/
,%
,=
,<
,>
,<=
,>=
,==
,!=
,<<
,>>
,|
,&
,^
,?
,:
) отделяются слева и справа пробелами. Например:a + b
вместоa+b
. Унарные операторы пробелом от аргумента не отделяются. Например:++a
,b->c
,k = -a
McCabe complexity не должна превышать 6 для простых функций и 9 для сложных. Сложные функции, McCabe complexity которых превышает 6 должны содержать локально задокументированное описание, доказывающее, что именно такой вариант реализации является оптимальным. Пример расчета:
int somefunc(int a, int b, int c) { // McCabe = 1 at this point // statements denoted as ... here affect the control flow // McCabe is all about graph and possible paths, used to reach // from function call to return // we have two execution variants (if, else), each adds +1 to complexity if (a) { ... } else { ... } // at this point it would have been equal to 1 + 2 = 3 // but we have more to offer: // add +1 if (b) { // add +2 if (a == c) { ... } else { ... } } // at this point it would have been equal to 1 + 1 + 2 = 4 // but we have even more to offer: // add +1 if (c) { // add +3 if (b) { ... } else if (c) { ... } else { ... } // add +1 as we have two possibilities: // having return here and notreturned() acts as if-else return 0; } notreturned(); return -1; // at this point it is equal to 1 + 1 + 3 + 1 = 6 // add one more indentation level and you're screwed }
Код должен быть задокументирован согласно требованиям kernel-doc. Комментарии делятся на "внешние" и "внутренние". Внешние затем используются для автоматической сброрки документации. Внутренние же предназначены для разработчика, чтобы повысить читабельность кода, объяснить какие-то неочевидные моменты.
При этом, следует избегать как недостаточной, так и излишней документированности. Например:
Излишне. Тут и так понятно, как расшифровывается
cnt
и где используется.int cnt = 0; // counter while (cnt++ < k) { ... }
Недостаточно. Тут не мешало бы описать, зачем мы трактуем
somevar
какunsigned long long
и какие возможны сайд-эффекты. А также откуда взялась эта магическая константа. В большинстве случаев, константы лучше определить какconst
и использовать в коде, обращаясь по имени.tmp = *((unsigned long long *)&somevar) & 0xD0BF00AA00000000LLU;
Более подробно о необходимой степени документированности можно почитать в Coding Style.
Использование include guards, в большинстве случаев, считается плохой практикой и недопустимо. То же относится и к
#pragma once
. Необходимость в данных конструкциях свидетельствует о неправильной декомпозиции и наличии циклических зависимостей. Ниже проиллюстрирован пример такой зависимости и способ борьбы с ней:Стоит заметить, что циклические зависимости могут возникать и в пределах одного файла. В этом случае, для борьбы с ними используют forward declaration. Пример структуры, содержащей в качестве одного из полей указатель на функцию, принимающую аргументом объект типа этой структуры:
/* Forward declaration */ struct niceobj; /* * Pointer named comfunc to function of the form * void name(struct niceobj *) * defined as type */ typedef void (*comfunc)(struct niceobj *); /* Here we finally declare it */ struct niceobj { /* Use the defined type */ comfunc comfuncptr; /* Simply pointer named otherfunc without type definition * This one can be used without forward declaration */ void (*otherfunc)(struct niceobj *, int); ... }; /* Here we have some function pointers used */ void comcom(struct niceobj *obj) { ... } void otherother(struct niceobj *obj, int somearg) { ... } static const struct niceobj nice = { .comfuncptr = &comcom, .otherfunc = &otherother }; ... /* Use it */ a = nice.comcom(&nice); b = nice.otherfunc(&nice, 0); /* Or explicitly like this */ a = (*nice.comcom)(&nice); b = (*nice.otherfunc)(&nice, 0);
typedef
используется для opaque-объектов, внутренняя структура которых частично (лучше – полностью) сокрыта от конечного пользователя. Подразумевается, что пользователю не нужно знать как объект устроен внутри. Для работы с таким объектом пользователь использует методы. Пример такого объекта:typedef struct unit_s { void *ptr; size_t size; } defunit_t; size_t defunit_getsize(defunit_t *unit); defunit_t *defunit_create(void *ptr, size_t size); ...
Стоит заметить, что даже у структуры, объявленной типом, есть имя (
typedef struct unit_s {...
вместо простоtypedef struct {...
). Это необходимо для упрощения отладки. Следует по возможности избегать анонимного, особенно структур и перечислений (кроме случая, когда перечисления хранят константы).Пример transparent-объекта, который не скрывает структуру от пользователя:
struct vecpoint { long long x; long long y; }; struct vecpoint to_vector(struct vecpoint *p1, struct vecpoint *p2) { struct vecpoint ret = { .x = p2->x - p1->x, .y = p2->y - p1->y }; return ret; }
Также возможны объекты смешанного вида, часть полей которых сокрыта от пользователя, а часть является открытой. Это очень популярно в ядре. Например, пользователь создает структуру и заполняет определенные поля. Далее вызывает метод
init()
, который дозаполняет остальное. В таких случаях не стоит использоватьtypedef
, вместо этого, для сокрытия полей используется mangling-схема (когда, например, к имени приватных полей добавляется префикс или суффикс, указывающий на приватность:\_
,\__
,pv_
, ...Для всех имен используется стиль наименования lowercase_underscored_style, кроме:
- констант и макроопределений, для них используется UPPERCASE_UNDERSCORED_STYLE
- имен псеводо-ООП типов, для которых используется CamelCaseStyle
Пример псеводо-ООП:
/* Compiled with -fms-extensions */ typedef struct SomeAnimal_s { double kgweight; char *name; } SomeAnimal_t; /* Inherits Animal */ typedef struct HumanPerson_s { SomeAnimal; char *surname; } HumanPerson_t; /* ! NOTICE ! SomeAnimal is in CamelCase and is prefixed, so that all * methods belonging to it start with SomeAnimal_ * while the right part stays in lowercase_underscored_style */ void SomeAnimal_print(SomeAnimal_t *s) { printf("name: \"%s\", weight: %.2f\n", s->name, s->kgweight); } ... HumanPerson_t j = { .kgweight = 80.0, .name = "John", .surname = "Sins" }; SomeAnimal_t bee = { .kgweight = 0.25, .name = "Queen Bee" }; SomeAnimal_t *ptrs[] = {(SomeAnimal_t *)&j, (SomeAnimal_t *)&bee}; for (int i = 0; i < sizeof(ptrs)/sizeof(*ptrs); i++) { SomeAnimal_print(ptrs[i]); } /* Outputs * name: "John", weight: 55.00 * name: "Queen Bee", weight: 0.25 */
В коде, комментариях к нему, названиях переменных, а также документации не допускается использование других языков, кроме английского. Использование транслита также не допускается. Исключения составляют:
строки локализации, например:
struct lang lang_UA = { .exit_label = "Вийти"; .create_label = "Створити"; ... };
собственные имена. В данном случае используются правила транслитерации языка оригинала. Например:
В коде драйвера датчика 强光, он может быть записан как qiangquang_sensor
char ≠ byte. В соответствии со стандартом С:
sizeof(short)
≤sizeof(int)
≤sizeof(long)
≤sizeof(long long)
char
может быть равен илиsigned char
илиunsigned char
, должен иметь ширину, как минимум 8 бит. При этом его ширина должна быть достаточной, для того, чтобы уместить все символы из execution character setshort
иint
должны иметь ширину, как минимум 16 битlong
должен иметь ширину, как минимум 32 битаlong long
должен иметь ширину, как минимум 64 бита
Соответственно все вышеприведенные типы могут иметь одинаковую ширину. Если нужны байты, или другие типы фиксированной ширины, стоит использовать uint8_t, u8 и другие портативные типы.
float
– тип числа с плавающей запятой одинарной точности. Обычно это 32-битный IEEE 754 single-precision формат.Литерал для определения
float
этоF
илиf
, например:3.14F
. Тогда как3.14
этоdouble
double
– тип числа с плавающей запятой двойной точности. Как правило это 64-битный IEEE 754 double-precision формат.long double
– тип числа с плавающей запятой расширенной точности. Реализации существенно отличаютсяЛитерал для определения
long double
этоL
илиl
, например:3.14L
.
Если функция не принимает аргументов, в декларации и имплементации используется
somefunc(void)
, а не простоsomefunc()
.somefunc()
означает, что на этапе компиляции ничего не известно об аргументах функции. При вызовах все так же используетсяsomefunc();
- Студент при желании может добавить в код и/или сопроводительную документацию сведения об авторстве
- Студент обязан самостоятельно выбрать лицензию, под которой публикуются его наработки в репозитории.
Лицензия может применяться как ко всей директории студента в репозитории (в этом случае
LICENSE
необходимо положить в корень), либо для каждой из работ в отдельности (в этом случаеLICENSE
будет лежать в директории отдельной работы). При работе с ядром Linux и другими проектами необходимо обеспечить неконфликтность выбранной лицензии с лицензией проекта. - Заимствование элегантных решений приветствуется. В IT все крутится вокруг Code Reuse. Незачем пытаться повторить удачный алгоритм сортировки, если он уже имеется, хорошо документирован и был протестирован поколениями разработчиков
- Каждый заимствованный фрагмент должен содержать указание на: источник, автора, лицензию на использование. Заимствование изображений, фрагментов документации, экспериментальных данных и даже идей также являются заимствованиями и должны содержать вышеприведенные указания
- Заимствование не допускается, в случаях если:
- Это приводит к конфликту лицензий. Тем не менее, в таких случаях допускается реимплементация (код изучается, заимствуется идея, с должным указанием на первоисточник, код пишется заново без использования оригинального);
- Заимствованное решение является запатентованным;
- Заимствованный фрагмент является чрезмерно большим (составляет существенную часть реализации);
- Заимствованный фрагмент не дает преимуществ в сравнении с "наивной имплементацией";
- За нарушение правил заимствования, преподаватель оставляет за собой право применить одну или сразу несколько из штрафных санкций:
- Указать на необходимость устранения нарушения при проверке Pull Request;
- Оценить выполнение лабораторной работы в 0 баллов;
- Применить -1 балл к итоговому рейтингу студента (не более раза за каждую лабораторную работу, нарушающую правила заимствования).
Репозиторий имеет следующую структуру:
[+] Repository |----- .etc |--[+] dk71_ivanov | |----- LICENSE (см. выше) | |----- README.rst | |--[+] lab1_threaded_applications | | |----- LICENSE (см. выше) | | |----- README.rst | | |----- .gitignore | | |----- Makefile | | |--[+] src | | | |----- somefile.c | | | |----- ... | | | | | |--[+] inc | | | |----- somefile.h | | | |----- ... | | | | | |--[+] doc | | | |----- ... | | | | | |-- .... | | | |--[+] lab2_simple_kernel_module | | |-- .... | | | |--[+] rgr_you_chose_the_name | | |-- .... | |--[+] dk72_sidorov | |-- .... | |-- ....
Директории имеют названия в нижем_кейсе_с_подчеркиваниями. Использование пробелов в названиях файлов и директорий не допускается.
Названия индивидуальных директорий студента формируются по принципу
groupname_surname
, где surname — фамилия студента, как она записана в заграничном пасспорте или водительских правах, а если они отсутствуют — в соответствии с официальными правилами транслитерации.Названия директорий лабораторных работ не могут быть произвольными и выдаются преподавателем вместе с заданием. Названия других работ формируются по принципу
prefix_name
, где name — название работы на английском языке, выбираемое студентом самостоятельноДиректория каждой работы должна содержать:
README.rst
— электронный протокол к лабораторной работе (либо электронная пояснительная записка для других видов работ);LICENSE
— в случае, если она отсутствует в корневой (индивидуальной) директории студента или имеет место конфликт лицензий;Makefile
. Допускается использование любых систем сборки (кроме модулей ядра, собираемых Kbuild). Тем не менее, основные команды сборки (такие какhelp
all
clean
tidy
и другие) должны иметь возможность запускаться из-под make. При желании использовать отличную от make систему сборки, вышеприведенные рецепты make могут просто передавать управление используемой системе. В Makefile обязательна реализация целейall
,clean
,install
(если используется) и цели по умолчанию.После запуска цели
clean
, директория не должна содержать артефактов сборки (временных файлов, а также результатов сборки, если цельtidy
отсутствует);.gitignore
должен содержать исключения всех артефактов сборки. Другими словами, после запуска сборки и последующей попытки commit всей директории, git не должен подхватывать ни один из файлов, которых не было до запуска сборки. За основу можно взять gitignore от GitHubДиректории
src
(содержит исходники),inc
(заголовки) иdoc
(документация) имеют строго определенное назначение и должны присутствовать в директориях работ по мере необходимости (складывать исходники вместе с заголовками и документацией в корень не допускается)
В любой момент времени, репозиторий должен содержать лишь минимально необходимый набор исходных файлов, документации и другого, требуемого для сборки. Попадание результатов сборки в репозиторий категорически недопустимо.
Pull Request, содержащие коммиты с бинарными файлами (кроме изображений для документации, firmware устройств и подобного) будут отклоняться без объяснения причин. То же касается и документации: например, если документация собирается в .pdf-файл из .rst, нет необходимости включать в коммит сам .pdf
В каждом Pull Request допускается наличие не более двух коммитов. Первый используется для подачи выполненной работы на проверку, а второй — для исправления замечаний (если они имеются).
При внесении изменений в ветку, они автоматически будут подхвачены в Pull Request.
История коммитов master-ветки репозитория должна быть чистой и линейной. Если делаете fork, затем серию коммитов и затем подаете Pull Request, это значит, что в итоге в master-ветку попадут все промежуточные коммиты. Для избежания такой ситуации можно делать squash merge в отдельную ветку (из которой затем подавать Pull Request) или rebase.
Правильный Pull Request
Неправильный Pull Request
Перед подачей Pull Request необходимо убедиться, что вносимые изменения касаются только текущей работы и не затрагивают других файлов и директорий
В описании Pull Request указываются ключевые особенности реализации, которые не уместно указывать в
README.rst
(не нужно дублировать или вместо README писать в описание PR). Если по данной работе уже подавался PR и был отклонен по причинам некорректного оформления, к текущему PR необходимо добавить тег RE и прилинковать предыдущий PR к текущему (так чтобы GitHub подхватил).В Reviewers указываете любого из ваших коллег, с кем предварительно договорились.
Когда проводите Peer Review, пишите (используя средства GitHub) все найденные ошибки и предложения по их устранению. Автор PR, в свою очередь, может согласиться или не согласиться, высказать свою точку зрения и в итоге найти консенсус.
После Peer Review, автор PR добавляет преподавателя в Reviewers. Преподаватель указывает на не найденные предыдущим ревьюером ошибки, а также на те, которые ошибками не являются. В итоге преподаватель оставляет комментарий и студент может приступать к исправлению ошибок (если они имеются).
После исправления ошибок, преподаватель в очередной раз смотрит Pull Request и оценивает выполнение работы. Если PR был оформлен правильно (именно PR, вне зависимости от ошибок кода), после второго просмотра, преподаватель выполняет PR merge. С этого момента студент может приступать к защите работы. В случае, если {R был оформлен не правильно, он закрывается преподавателем, а студенту необходимо создать новый со ссылкой на текущий (по вышеописанной процедуре).
Все найденные и не найденные в процессе ревью ошибки учитываются, ревьюер получает бонусные баллы за их нахождение, а автор PR — за их исправление.
Настоятельно не рекомендуется злоупотреблять механизмом ревью, ведь в итоге все смотрит преподаватель.