JavaRush /Java блогы /Random-KK /Код ережелері: дұрыс атау, жақсы және жаман пікірлердің к...
Константин
Деңгей

Код ережелері: дұрыс атау, жақсы және жаман пікірлердің күші

Топта жарияланған
Код жазу ережелері: дұрыс атау, жақсы және жаман пікірлердің күші - 1 Сізге басқа біреудің codeын қаншалықты жиі түсінуге тура келді? Бір-екі сағаттың орнына сіз не болып жатқанының логикасын түсіну үшін күндерді өткізесіз. Бір қызығы, бұл codeты жазған адам үшін бәрі түсінікті және өте мөлдір. Және бұл таңқаларлық емес: сайып келгенде, мінсіз немесе идеалды code - бұл өте анық емес түсінік, өйткені әрбір әзірлеушінің сәйкесінше әлемге және codeқа деген өзіндік көзқарасы бар. Әріптесім екеуміз бір codeты қарап, оның дұрыстығы мен тазалығы туралы әртүрлі пікірде болған жағдайды бірнеше рет кездестірдім. Код жазу ережелері: дұрыс атау, жақсы және жаман пікірлердің күші - 2Бұл таныс сезім, солай емес пе? Дегенмен, ұстануға тиіс кейбір уақыт тексерілген нюанстар бар, олар сайып келгенде біздің пайдамызға жұмыс істейді, өйткені егер сіз codeты өзіңіз алғыңыз келетін күйде қалдырсаңыз, әлем сәл бақыттырақ болар еді және тазалаушы. Код жазу ережелері туралы соңғы мақаламыздаКод жазу ережелері: дұрыс атау, жақсы және жаман пікірлердің күші - 3 ( дәлірек айтсақ, кішігірім нұсқаулық) біз жүйені тұтастай және оның an objectілері, интерфейстері, кластары, әдістері мен айнымалылары сияқты элементтерін жазу бойынша ұсыныстарға аздап тоқталдық. Онда мен кейбір элементтердің дұрыс аталуын қысқаша айттым. Бүгін мен дәл осы туралы айтқым келеді, өйткені дұрыс атаулар codeты оқуды жеңілдетеді. Біз дұрыс code тақырыбын ойлар мен codeтағы түсініктемелердің шағын мысалдары арқылы жабамыз - бұл жақсы ма, әлде жақсы емес пе. Ендеше, бастайық.

Дұрыс атау

Дұрыс атаулар codeтың оқылуын жақсартады, сәйкесінше танысу уақытын үнемдейді, себебі атау оның функционалдығын шамамен сипаттайтын кезде әдісті пайдалану әлдеқайда оңай. Кодтағы барлық нәрсе атаулардан (айнымалылар, әдістер, сыныптар, файлдық нысандар және т.б.) тұратындықтан, бұл нүкте дұрыс, таза codeты жасау кезінде өте маңызды болады. Жоғарыда айтылғандарға сүйене отырып, атау, мысалы, айнымалының неліктен бар екенін, оның не істейтінін және қалай қолданылатынын білдіруі керек. Мен айнымалыны сипаттау үшін ең жақсы түсініктеме оның дұрыс атауы екенін қайта-қайта атап өтемін. Код жазу ережелері: дұрыс атау, жақсы және жаман пікірлердің күші - 4

Интерфейстерді атау

Интерфейстер әдетте бас әріптен басталатын және түйе регистрінде (CamelCase) жазылатын атауларды пайдаланады. Интерфейсті интерфейс ретінде (мысалы, IUserService) белгілеу үшін оны I префиксімен жазу интерфейсін жазу кезінде жақсы тәжірибе болды, бірақ бұл өте жағымсыз және алаңдатады. Мұндай жағдайларда онсыз (UserService) жазып, оны іске асыруға -Impl (UserServiceImpl) қосқан дұрыс. Жақсы немесе соңғы шара ретінде оны іске асыруға C префиксін (CUserService) қосыңыз.

Сынып аттары

Интерфейстер сияқты, атаулар бас әріппен жазылады және түйе стилін (CamelCase) пайдаланады. Қандай ақырзаман болып жатқанына қарамастан, мерзімдер қаншалықты жылдам болса да, бірақ ешқашан, есіңізде болсын, сыныптың атауы ешқашан етістік болмауы керек! Класс және нысан атаулары зат есімдер және олардың тіркесімі болуы керек (UserController, UserDetails, UserAccount және т.б.). Әрбір сыныптың атын осы қолданбаның аббревиатурасымен бермеу керек, өйткені бұл тек қажетсіз күрделілікті қосады (мысалы, бізде Пайдаланушы деректерін тасымалдау қолданбасы бар және біз әрбір сыныпқа UDM қосамыз - UDMUserDeatils, UDMUserAccount, UDMUserController ).

Әдіс атаулары

Әдетте әдістердің атаулары кіші әріптен басталады, бірақ оларда түйе стилі де қолданылады (CamelCase). Жоғарыда біз сынып атауларының ешқашан етістік болмауы керектігі туралы айттық. Мұнда жағдай диаметральді қарама-қарсы: әдістердің атаулары етістіктер немесе олардың етістіктермен тіркесімі болуы керек: findUserById, findAllUsers, createUser және т.б. Әдісті (сонымен қатар айнымалылар мен сыныптарды) жасау кезінде шатастырмау үшін бір атау тәсілін пайдаланыңыз. Мысалы, пайдаланушыны табу үшін әдіс getUserById немесе findUserById ретінде жазылуы мүмкін. Және тағы бір нәрсе: әдістер атауларында әзіл-оспақ қолданбаңыз, өйткені олар әзілді түсінбеуі мүмкін, сонымен қатар бұл әдіс не істейді.

Айнымалы атаулар

Көп жағдайда айнымалы атаулары кіші әріптен басталады және сонымен қатар айнымалы жаһандық тұрақты болатын жағдайларды қоспағанда, Camelcase пайдаланады. Мұндай жағдайларда атаудың барлық әріптері бас әріппен жазылады және сөздер астын сызу арқылы бөлінеді - «_». Айнымалыларды атаған кезде ыңғайлы болу үшін мағыналы контекстті пайдалануға болады. Басқаша айтқанда, үлкенірек нәрсенің бөлігі ретінде айнымалы болған кезде - мысалы, FirstName, Famile, status - мұндай жағдайларда осы айнымалы бөлігі болып табылатын нысанды көрсететін префиксті қосуға болады. Мысалы: userFirstName, userLastName, userStatus. Сондай-ақ айнымалылар мүлдем басқа мағынаға ие болған кезде ұқсас атауларды пайдаланудан аулақ болу керек. Айнымалыларға ортақ антонимдер:
  • бастау/аяқтау
  • бірінші/соңғы
  • құлыпталған/құлпы ашылған
  • мин/макс
  • келесі/алдыңғы
  • ескі/жаңа
  • ашылды/жабылды
  • көрінетін/көрінбейтін
  • көз/мақсат
  • көз/тағайындау
  • жоғары-төмен

Қысқа айнымалы атаулар

Бізде x немесе n немесе сол сияқты айнымалылар болғанда, біз codeты жазған адамның ниетін бірден байқамаймыз. n әдісінің не істейтіні анық емес: ол көп ойластырылған ойлауды талап етеді (және бұл уақыт, уақыт, уақыт). Мысалы, бізде өріс бар - жауапты пайдаланушының идентификаторы және x немесе жай идентификатор сияқты кейбір атаулардың орнына біз бұл айнымалыны жауаптыUserId деп атаймыз, ол бірден оқылу мен мағыналылықты арттырады. Дегенмен, n сияқты қысқа атаулар кішігірім әдістерге жергілікті өзгертулер ретінде орын алады, мұнда осы өзгерісі бар code блогы codeтың бірнеше жолынан тұрады және әдіс атауы онда не болып жатқанын тамаша сипаттайды. Әзірлеуші ​​мұндай айнымалыны көріп, оның екінші маңыздылығын және өте шектеулі ауқымын түсінеді. Нәтижесінде айнымалы атауының ұзындығына белгілі бір тәуелділік бар: ол неғұрлым ұзақ болса, соғұрлым айнымалы жаһандық болады және керісінше. Мысал ретінде соңғы сақталған пайдаланушыны күні бойынша табу әдісі:
public User findLastUser() {
   return findAllUsers().stream()
           .sorted((x, y) -> -x.getCreatedDate().compareTo(y.getCreatedDate()))
           .findFirst()
           .orElseThrow(() -> new ResourceNotFoundException("Any user doesn't exist "));
}
Мұнда біз ағынды сұрыптауды орнату үшін x және y қысқа атауларын қолданамыз және олар туралы ұмытып кетеміз.

Оңтайлы ұзындық

Атау ұзындығы тақырыбын жалғастырайық. Оңтайлы атау ұзындығы максималдыNumberOfUsersInTheCurrentGroup атауы ұзындығы мен n арасында болады. Яғни, тым қысқалары мағынаның жетіспеушілігінен зардап шегеді, ал тым ұзындары оқылуды қоспай, бағдарламаны созады және оларды әр уақытта жазуға тым жалқау. Жоғарыдағы жағдайды ескермей, n сияқты қысқа атауы бар айнымалылар үшін ұзындығын шамамен 8 -16 таңбаға дейін сақтау керек. Бұл қатаң ереже емес: көбірек нұсқаулық.

Шағын айырмашылықтар

Мен атаулардағы нәзік айырмашылықтарды елеусіз қалдыра алмаймын, өйткені бұл да жаман тәжірибе, өйткені сіз жай ғана шатастыра аласыз немесе атаулардағы шамалы айырмашылықтарды байқап, көп уақыт жұмсай аласыз. Мысалы, InvalidDataAccessApiUsageException және InvalidDataAccessResourceUsageException арасындағы айырмашылықты бір қарағанда анықтау қиын. Сондай-ақ, кішігірім L және O пайдалану кезінде жиі қате ақпарат туындауы мүмкін, себебі оларды 1 және 0 сандарымен оңай шатастыруға болады: кейбір қаріптерде айырмашылық айқынырақ, басқаларында азырақ.

Семантикалық бөлігі

Бізге семантикалық бөлікті атауларға қою керек, бірақ синонимдермен артық ойнатпау керек, өйткені, мысалы, UserData және UserInfo бір мағынаға ие және бізге қандай нақты an object қажет екенін түсіну үшін codeты біраз тереңірек зерттеу керек болады. . Ақпаратсыз сөздерден аулақ болыңыз, мысалы, firstNameString: бізге жол сөзі не үшін қажет? Атау күн түрінің нысаны бола ала ма? Әрине жоқ: сондықтан, жай ғана - FirstName. Мысал ретінде логикалық айнымалыларды атап өткім келеді, мысалы, flagDelete. Ту сөзінің семантикалық мағынасы жоқ. Оны isDelete деп атаған жөн болар еді.

Дезинформация

Сондай-ақ, қате атау туралы бірнеше сөз айтқым келеді. Бізде userActivityList атауы бар делік және осылай аталған нысан Тізім түріне жатпайды, бірақ сақтауға арналған басқа контейнер немесе пайдаланушы нысаны. Бұл қарапайым бағдарламашыны шатастыруы мүмкін: оны userActivityGroup немесе userActivities деп атаған дұрыс.

Іздеу

Қысқа және қарапайым атаулардың кемшіліктерінің бірі оларды codeтың үлкен көлемінен табу қиын, себебі нені табу оңайырақ: name немесе NAME_FOR_DEFAULT_USER деп аталатын айнымалы ма? Әрине, екінші нұсқа. Атауларда жиі кездесетін сөздерден (әріптерден) аулақ болу керек, өйткені бұл іздеу кезінде табылған файлдардың санын көбейтеді, бұл жақсы емес. Бағдарламашылар оны жазудан гөрі codeты оқуға көбірек уақыт жұмсайтынын еске салғымыз келеді, сондықтан қолданбаңыздың элементтерінің атауын есте сақтаңыз. Бірақ егер сіз оны сәтті атай алмасаңыз ше? Әдістің атауы оның функционалдығын жақсы сипаттамаса ше? Міне, бұл ойынға түседі, біздің келесі тармақ - пікірлер.

Пікірлер

Код жазу ережелері: дұрыс атау, жақсы және жаман пікірлердің күші - 5Тиісті түсініктеме сияқты ештеңе жоқ, бірақ мағынасыз, ескірген немесе жаңылыстыратын түсініктемелер сияқты модульді ештеңе бұзбайды. Бұл екі жүзді қылыш, солай емес пе? Дегенмен, сіз түсініктемелерді бір мағыналы жақсылық ретінде қарастырмауыңыз керек: керісінше, азырақ зұлымдық ретінде. Өйткені, түсініктеме өзінің мәні бойынша codeта сәтсіз айтылған ойдың орнын толтыру болып табылады. Мысалы, егер ол тым түсініксіз болып шықса, біз оларды әдістің мәнін жеткізу үшін қолданамыз. Мұндай жағдайда сипаттаушы жазбаларды жазудан гөрі codeты дұрыс қайта өңдеу жақсы. Түсініктеме неғұрлым ескі болса, соғұрлым нашар, өйткені code өсу мен дамуға бейім, бірақ түсініктеме сол күйінде қалуы мүмкін және ол неғұрлым ұзақ болса, бұл жазбалар соғұрлым күмәнді болады. Дәл емес пікірлер түсініктеме бермеуден әлдеқайда нашар, өйткені олар шатастырады және жалған үміттер береді. Бізде өте күрделі code болса да, ол туралы түсініктеме бермей, оны қайта жазу керек.

Түсініктемелердің түрлері

  • Заңды түсініктемелер – әрбір бастапқы code файлының басында келесідей заңды себептермен қалдырылған түсініктемелер:

    * Copyright (c) 2007, 2013, Oracle and/or its affiliates. All rights reserved.
    * ORACLE PROPRIETARY/CONFIDENTIAL. Use is subject to license terms.

  • Ақпараттық түсініктемелер – codeтың түсіндірмесін беретін (қосымша ақпаратты немесе берілген code бөлігінің мақсатын беретін) түсініктемелер.

    Мысал ретінде:

    /*
    * Объединяет пользователя из бд и пришедшего для обновления
    * Когда в requestUser поле пустое, оно заполняется старыми данными из foundUser
    */
    private User mergeUser(User requestUser, User foundUser) {
           return new User(
           foundUser.getId(),
           requestUser.getFirstName() == null ? requestUser.getFirstName() : foundUser.getFirstName(),
           requestUser.getMiddleName() == null ? requestUser.getMiddleName() : foundUser.getMiddleName(),
           requestUser.getLastName() == null ? requestUser.getLastName() : foundUser.getLastName(),
           requestUser.getAge() == null ? requestUser.getAge() : foundUser.getAge()
           );
           }

    Бұл жағдайда сіз түсініктемесіз жасай аласыз, өйткені әдістің атауы және оның дәлелдері өте мөлдір функционалдылықпен бірге өзін жақсы сипаттайды.

  • ескерту түсініктемесі - мақсаты басқа әзірлеушілерге қандай да бір әрекеттің жағымсыз салдары туралы ескерту болып табылатын түсініктеме (мысалы, сынақ неліктен @Ignore деп белгіленген):

    // Слишком долго отрабатывает
    // Не запускайте, если не располагаете избытком времени
    @Ignore
    @Test
    public void someIntegrationTest() {
           ……
           }
  • TODO - болашаққа арналған ескертпелер, оларды орындау қажет, бірақ қандай да бір себептермен қазір орындау мүмкін емес. Бұл жақсы тәжірибе, бірақ олар әлі де ретсіздікке жол бермеу үшін қажетсіздерін жою үшін үнемі тексеріліп отыруы керек.

    Примером послужит:

    //TODO: Add a check for the current user ID (when will be created security context)
    
    @Override
    public Resource downloadFile(File file) {
           return fileManager.download(file);
           }

    Тут мы помечаем, что нужно добавить проверку юзера, который скачивает (id которого мы вытащим из security контекста) с тем, кто сохранил.

  • усorвающий комментарий — комментарий, подчеркивающий важность Howого-то обстоятельства, что на первый взгляд может показаться несущественным.

    Как пример, кусочек метода, заполняющий тестовую БД, некими скриптами:

    Stream.of(IOUtils.resourceToString("/fill-scripts/" + x, StandardCharsets.UTF_8)
           .trim()
           .split(";"))
           .forEach(jdbcTemplate::update);
    // Вызов trim() очень важен, убирает возможные пробелы в конце скрипта
    // чтобы при считке и разбивке на отдельные requestы не было пустых

  • javaDoc — комментарии, которые описывают API определенного функционала для общего пользования. Наверное, самые полезные комментарии, так How с documentированным API в разы легче работать, но они также могут устаревать, How и любые другие. Поэтому не забываем, что главный вклад в documentацию вносится не комментариями, а хорошим codeом.

    Пример вполне обычного метода обновления пользователя:

    /**
    * Обновляет передаваемые поля для пользователя по id.
    *
    * @param id  id обновляемого пользователя
    * @param user пользователь с заполненными полями для обновления
    * @return обновленный пользователь
    */
           User update(Long id, User user);

Плохие сценарии комментариев

Код жазу ережелері: дұрыс атау, жақсы және жаман пікірлердің күші - 7
  • бормочущий комментарий — комментарии, которые обычно пишут на скорую руку, смысл которых понятен только разработчику, писавшего их, так How только он видит ту ситуацию с теми нюансами, на которые он и ссылается.

    Рассмотрим данный пример:

    public void configureSomeSystem() {
           try{
           String configPath = filesLocation.concat("/").concat(CONFIGURATION_FILE);
           FileInputStream stream = new FileInputStream(configPath);
           }  catch (FileNotFoundException e) {
           //В случае отсутствия конфигурационного file, загружается конфигурация по умолчанию
          }
    }

    Кто загружает эти настройки? Были ли они загружены ранее? Метод предназначен для перехвата исключений и вызова дефолтных настроек? Слишком много вопросов возникает, ответы на которые можно получить лишь углубившись в изучение других частей системы.

  • избыточный комментарий — комментарий, который не несёт смысловой нагрузки, так How и так понятно что происходит в заданном участке codeа (он читается не проще, чем code).

    Смотрим пример:

    public class JdbcConnection{
    public class JdbcConnection{
       /**
        * Журнальный компонент, связанный с текущим классом
        */
       private Logger log = Logger.getLogger(JdbcConnection.class.getName());
    
       /**
        * Создаёт и возвращает connection с помощью входящих параметров
        */
       public static Connection buildConnection(String url, String login, String password, String driver) throws Exception {
           Class.forName(driver);
           connection = DriverManager.getConnection(url, login, password);
           log.info("Created connection with db");
           return connection;
       }

    Какой смысл таких комментариев, если мы и так всё прекрасно видим

  • недостоверные комментарии — комментарии, не соответствующие истине и лишь вгоняющие в заблуждение (дезинформирующие). Как например:

    /**
    * Вспомогательный метод, закрывает соединение со сканером, если isNotUsing истинно
    */
    private void scanClose(Scanner scan, boolean isNotUsing) throws Exception {
       if (!isNotUsing) {
           throw new Exception("The scanner is still in use");
       } scan.close();
    }

    What в этом комменте не так? А то, что он немножко врёт нам, ведь соединение закрывается, если isNotUsing = false, но ниHow не наоборот, How нам вещает пометка.

  • обязательные комментарии — комментарии, которые считают обязательными (Javadoc), но кои по факту иногда бывают излишне нагромождающими, недостоверными и ненужными (нужно задуматься, а нужны ли здесь такие комментарии).

    Пример:

    /**
    *  Creation пользователя по переданным параметрам
    * @param firstName Name созданного пользователя
    * @param middleName среднее Name созданного пользователя
    * @param lastName фамorя созданного пользователя
    * @param age возраст созданного пользователя
    * @param address addressс созданного пользователя
    * @return пользователь который был создан
    */
    User createNewUser(String firstName, String middleName, String lastName, String age, String address);

    Смогли бы вы понять, что делает метод без этих комментариев? Скорее всего да, поэтому комментарии в этом случае стают бессмысленными.

  • журнальные комментарии — комментарии, которые иногда добавляют в начало модуля, при каждом его редактировании (что-то вроде журнала вносимых изменений).

    /**
    *  Записи ведутся с 09 января 2020;
    **********************************************************************
    *  09.01.2020  : Обеспечение соединения с БД с помощью Jdbc Connection;
    *  15.01.2020  : Добавление интерфейсов уровня дао для работы с БД;
    *  23.01.2020  : Добавление интеграционных тестов для БД;
    *  28.01.2020  : Имплементация интерфейсов уровня дао;
    *  01.02.2020  : Разработка интерфейсов для сервисов,
    *  согласно требованиям прописанным в user stories;
    *  16.02.2020  : Имплементация интерфейсов сервисов
    *  (реализация бизнес логики связанной с работой БД);
    *  25.02.2020  : Добавление тестов для сервисов;
    *  08.03.2020  : Празднование восьмого марта(Миша опять в хлам);
    *  21.03.2020  : Рефакторинг сервис слоя;
    */

    Когда-то этот проход был оправдан, но с появлением систем управления исходным codeом (например — Git), это стало лишним нагромождением и усложнением codeа.

  • комментарии ссылки на авторов — комментарии, преднаmeaningм которых является, указание человека, писавшего code, чтобы можно было связаться и обсудить, How что и зачем:

    * @author  Bender Benderovich

    Опять же, системы контроля версий прекрасно запоминают, кто и когда добавил данный code, и подобный подход излишен.

  • Түсініктеме берілген code - бір немесе басқа себептермен түсініктеме берілген code. Ең жаман әдеттердің бірі, өйткені сіз оны түсіндіріп, ұмытып қалдыңыз, ал басқа әзірлеушілердің оны жоюға батылдары жетпейді (бұл құнды нәрсе болса ше).

    //    public void someMethod(SomeObject obj) {
    //    .....
    //    }

    Нәтижесінде ол қоқыс сияқты жиналады. Ешбір жағдайда мұндай codeты қалдыруға болмайды. Егер сізге шынымен қажет болса, нұсқаны басқару жүйесі туралы ұмытпаңыз.

  • айқын емес түсініктемелер — бір нәрсені қажетсіз күрделі түрде сипаттайтын пікірлер.

    /*
        * Начать с массива, размер которого достаточен для хранения
        * всех byteов данных (плюс byteы фильтра) с запасом, плюс 300 byte
        * для данных заголовка
        */
    this.dataBytes = new byte[(this.size * (this.deep + 1) * 2)+300];

    Түсініктеме codeты түсіндіруі керек, түсіндірудің өзі қажет емес. Мұнда не бар? «Сүзгі byteтар» дегеніміз не? +1 бұған не қатысы бар? Неліктен дәл 300?

Егер сіз түсініктеме жазуды шешсеңіз, оларды пайдалану бойынша бірнеше кеңестер:
  1. Күту оңай стильдерді пайдаланыңыз: тым сәнді және экзотикалық стильдерді сақтау тітіркендіргіш және уақытты қажет етуі мүмкін.
  2. Жалғыз жолдарға сілтеме жасайтын жолдардың соңында түсініктемелерді пайдаланбаңыз: бұл пікірлердің үлкен үйіндісін жасайды және әрбір жолға мәнерлі түсініктеме беру қиын.
  3. Түсініктеме жасау кезінде «қалай» емес, «неге» деген сұраққа жауап беруге тырысыңыз.
  4. Қысқартулардан аулақ болыңыз. Жоғарыда айтқанымдай, түсініктемеге түсініктеме қажет емес: түсініктеме - түсініктеме.
  5. Өлшем бірліктерін және қолайлы мәндер ауқымын белгілеу үшін түсініктемелерді пайдалануға болады.
  6. Түсініктемелерді олар сипаттайтын codeқа жақын орналастырыңыз.
Нәтижесінде мен әлі де еске салғым келеді: ең жақсы пікірлер - бұл түсініктеменің болмауы және оның орнына қосымшада дұрыс атау. Әдетте, біз көп жағдайда дайын codeпен жұмыс істейміз, оны сақтаймыз және кеңейтеміз. Бұл codeты оқуға және түсінуге оңай болған кезде әлдеқайда ыңғайлы, өйткені нашар code жолға түсіп, дөңгелектерге спиц қояды, ал асығыс оның сенімді серігі болып табылады. Бізде қаншалықты нашар code болса, соғұрлым өнімділік төмендейді, сондықтан біз мезгіл-мезгіл рефакторлауымыз керек. Бірақ егер сіз басынан бастап codeты жазуға тырыссаңыз, ол үшін кейінгі әзірлеушілер сізді тауып өлтіргісі келмейтін болса, онда сізге оны жиірек қайта өңдеу қажет болады. Бірақ бұл әлі де қажет болады, өйткені өнімге қойылатын талаптар мен талаптар үнемі өзгеріп отырады, қосымша қосылымдарды қосу арқылы толықтырылады және одан құтылу мүмкін емес. Соңында, мен сізге осы тақырыппен танысу үшін бірнеше қызықты сілтемелер қалдырамын , мұнда және осында , мен үшін бәрі бүгін, оқығандардың барлығына рахмет)) Код жазу ережелері: дұрыс атау, жақсы және жаман пікірлердің күші - 8
Пікірлер
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION