Первая моя ассоциация с Github Copilot - это резиновый мужик-автопилот из фильма "Аэроплан". Помню, в детстве я увидел этот фильм и потом реально думал, что так автопилоты и выглядят: какая-то смешная резиновая кукла надувается и начинает управлять самолётом. И вот, через столько лет меня не покидает ощущение, что передо мной надувается какая-то кукла и пытается писать за меня код.

Короче, нежданно-негаданно мне пришёл инвайт в GitHub copilot. Я и забыл про то, что когда-то запрашивал доступ - ажиотаж давно уже спал, вроде мир не захвачен машинами, а значит, не такой уж он и крутой, да? С другой стороны, недавно к нам на позицию senior python постучал чел и сказал, что большую часть нашего тестового задания он запилил при помощи copilot, так что определённо кто-то "пилотом" пользуется. В общем, непонятно: хорошо оно или плохо? Стану ли я теперь более продуктивным? Есть только один способ проверить: в бою.

Как тестировать?

И тут я задумался.

Реальные проекты разные.

В copilot ты пишешь комментарий или код, и по нему работает автодополнение. И вот тут мне показалось плохой идеей писать что-то вроде:

# this is a bullshit code written by previous developer, 
# zero tests (of course), please run it and see where it breaks 
# and try to fix; also update all dependencies to latest 
# (read changelogs and migrate db and code where needed)

Нет, ну конечно, я попробовал, но мужик написал мне только

def bullshit_code():

Короче, такие абстрактные вещи для copilot не подходят. Лучший кейс, как я понял - создание какой-нибудь функции, класса, метода или сниппета кода. Чего-то маленького, с ограниченной логикой.

Давайте всё это и попробуем. А под конец узнаем, кто всё-таки будет писать код: кожаный мешок или резиновый мужик.

Сразу предупрежу, что мужик может выдавать до 10 вариантов в режиме "выбирай что тебе больше нравится", но я буду больше всего внимания уделять первым ответам в списке, потому что

  1. первый вариант в списке - это вариант с наибольшей вероятностью (а copilot, как и gpt, просто предсказывает наиболее вероятное продолжение текста/кода), и значит у мужика этот первый ответ в приоритете (сугубо моё предположение)

  2. необходимость выбирать из 10 вариантов напрочь убивает всю идею помощника; если я должен читать 10 сниппетов кода и искать лучший, то я лучше и быстрее напишу сам

Мини-функции

Иногда нужно написать что-нибудь простое и раздражающее. Какую-нибудь мелкую функцию, которая по какой-то причине не попала в огромную stdlib. Мне это всегда было скучно писать, поэтому будет здорово, если у меня заберут эту рутину.

Разбитие по чанкам

Я прям беру описание из more_itertools::chunked

Break iterable into lists of length n

def chunks(lst, n):
    for i in range(0, len(lst), n):
        yield lst[i:i + n]

def chunks(lst, n):
    return [lst[i:i+n] for i in range(0, len(lst), n)]

Два варианта, один генератор, другой просто возвращает список. Это годно!

Lookahead без повреждения генератора

Тоже из more_itertools::spy

Return a 2-tuple with a list containing the first n elements of iterable, and an iterator with the same items as iterable. This allows you to “look ahead” at the items in the iterable without advancing it.

По факту мы должны создать кэш из n первых элементов, вернуть этот кэш и также вернуть "восстановленный" генератор.

def spy(iterable, n=1):
	  it = iter(iterable)  # о! превращаем iterable в iterable :)
    cache = []
    for i in range(n):
        try:
            cache.append(next(it))
        except StopIteration:
            break
    return cache, it

Вообще не то! Тут вроде есть cache, и возвращается даже правильная структура, но внутри у copilot произошла шизофазия. Должно быть что-то такое:

from itertools import chain, islice


def spy(iterable, n=1):
    head = list(islice(iterable, n))
    return head, chain(head, iterable)

Самая частая буква

Как-то на тестовом задании мне нужно было вернуть наиболее часто встречающуюся букву. Решение я родил за 5 секунд:

from collections import Counter


def most_common_letter(text: str) -> str:
    return Counter(text).most_common(1)[0][0]

Сначала мужик предлагает мне для каждого символа из текста вызывать text.count(symbol):

def most_common_letter(text: str) -> str:
    return max(text, key=text.count)

Я начинаю возникать, мол, может будем экономить ресурсы? Мужик предлагает: давай вызывать text.count(symbol) для каждого символа не более одного раза:

def most_common_letter(text: str) -> str:
    return max(set(text), key=text.count)

Может, встроенный Counter поюзаем?

"Окей," - говорит мне копилот, - "вот тебе твой Counter":

def most_common_letter(text: str) -> str:
    import collections
    return max(collections.Counter(text).items(), key=lambda x: x[1])[0]

Но Counter сам умеет считать максимум, а ты юзаешь max!

def most_common_letter(text: str) -> str:
    import collections
    return collections.Counter(text).most_common(1)[0][0]

Итого 4 варианта, из них два говно, два приемлемы.

RLE (run-length encoding)

Яндекс на собеседовании просил написать RLE функцию. Смог бы copilot работать в Яндексе?

# RLE (run-length encoding) function
def rle(string: str) -> str:
    result = ''
    for i in range(len(string)):  
        if i == 0:
            result += string[i]
        elif string[i] == string[i - 1]:
            result += '*'
        else:
            result += string[i]
    return result

# In [21]: rle('aaabcccd')
# Out[21]: 'a**bc**d'

Вообще не то! Кажется, мы только что убедились, что Copilot не умеет гуглить.

Я бы написал так:

from itertools import groupby

def rle(string):
    result = ''
    for char, group in groupby(string):
        result += f'{char}{len(list(group))}'
    return result
    
# In [35]: rle('aaabcccd')
# Out[35]: 'a3b1c3d1'

Генератор паролей

# generate random password
def generate_random_password(length: int) -> str:
    import random
    import string
    return ''.join(
      random.choice(string.ascii_letters + string.digits)
      for _ in range(length)
    )

Вроде ничо, но только copilot забыл, что random не настоящий рандом, и для паролей и прочих секретов есть secrets:

# use secrets to generate random password
def generate_random_password(secrets: str) -> str:
    import secrets
    return secrets.token_urlsafe(10)

Оверкилл. Я ожидал просто:

import secrets

password = secrets.token_urlsafe(length)

Генератор умножения на 2

# iterate over large list of numbers and multiply each number by 2
def double_numbers(numbers: list) -> list:
    return [x * 2 for x in numbers]

Я написал "iterate" и "large list", но мужик намёков не понял и возвращает список. Ну такое.

Сгруппируй слова

# group words that consist of same letters
def group_words(words: list) -> list:
    result = []
    for word in words:
        if word not in result:
            result.append(word)
    return result

Вместо того, что просили, копилот изобрёл уродливую версию list(set(words)), то есть просто вернул уникальные слова.

Удали элемент

# remove second element from list
def remove_second(lst: list) -> list:
    lst.pop(1)
    return lst

А вот это говно. Мужик передаёт мутабельный объект, модифицирует его и его же и возвращает.

Посмотрите, как сделано в питоне: reversed() возвращает копию и не модифицирует оригинал, а .reverse() ничего не возвращает и модифицирует оригинал.
Посмотрите, как сделано в pandas: apply() возвращает копию и не модифицирует оригинал, а с параметром inplace=True ничего не возвращает и модифицирует оригинал.

Должно быть что-то одно.

Что я понял

После большинства функций я выглядел так:

Тут ко мне пришло понимание, что для надувного мужика важно, чтобы ТЗ было чётким, а не как мы с вами привыкли: "напиши чонить шоб работало как надо". По опыту знаю, что документация и ТЗ могут отнимать много времени, так имеет ли смысл писать комментарий длиннее самой функции? Вот что надо написать, чтобы мужик сгенерировал годноту:

# function which receives password length and generates random password 
# of specified length; uses "secrets" module
def generate_random_password(length: int) -> str:
    import secrets
    return secrets.token_urlsafe(length)

Длинные функции

А давайте чтобы мужик писал больше, чем я? А то нечестно.

Класс мне запили

Сделай мне человека!

# person class
class Person:
    def __init__(self, name: str, age: int):
        self.name = name
        self.age = age

    def __str__(self):
        return f'{self.name}, {self.age}'

Алё! Ты из криокамеры вылез? Где датаклассы?

# person dataclass
@dataclass
class Person:
    name: str
    age: int

Так-то! Мы сделаем из тебя человека! Вообще можно ещё сделать @dataclass(slots=True, kw_only=True), но copilot мне не платит за обучение (но вы можете), так что учить я его не буду.

Просто сделай запрос

# makes async requests to url
async def make_request(url: str):
    import requests
    response = requests.get(url)
    return response.text

Тут бы какой-нить httpx...

Читай файл с комментариями

# read file exluding blank lines and lines starting with "#"
def read_file_exluding_blank_lines_and_lines_starting_with_hash(file: str) -> list:
    with open(file, 'r') as f:
        return [line for line in f if line and not line.startswith('#')]

А я-то думал, что это у меня проблемы с названиями для функций...

Опять же, почему не генератор? Файлы бывают большие.

Прочитай файл по кусочкам

# read file by chunks of 5 lines
def read_file_by_chunks(file: str) -> list:
    with open(file, 'r') as f:
        return [f.readlines(5)]

Не знаю, где и кто надувал этого мужика, но про генераторы он явно не слышал. А ещё он делает полную, тотальную хрень в этом примере: возвращает список, где единственый элемент - это первые 5 строчек файла.

Dict в файл

# write dictionary to file
def write_dictionary_to_file(dictionary: dict, file: str):
    with open(file, 'w') as f:
        for key, value in dictionary.items():
            f.write(f'{key} {value}\n')

Честно, я б не додумался записывать словарь как строчки ключ значение! А если ключ с пробелом, то как отличить ключ от значения: ключ хзчто значение?

Давайте намекнём на json:

# write dictionary to json file
def write_dictionary_to_json_file(dictionary: dict, file: str):
    import json
    with open(file, 'w') as f:
        json.dump(dictionary, f)

Почти!

  • file на самом деле надо назвать file_name

  • а вот f должен быть как раз file

  • и сдампил бы я с indent=2 хотя бы, чтобы человек тоже мог прочитать

Поменяй колонки в csv файле

# swaps 1st and 2nd csv columns
def swap(csv: str) -> str:
    return csv.replace(',', ';')

Ну ПОЧЕМУ?! За что?
Я попытался рассказать мужику про csv файлы, но он всё равно говорит, что поменять колонки - это заменить , на ;:

# swaps 1st and 2nd columns in csv file
def swap(infile: str, outfile: str):
    with open(infile, 'r') as f:
        lines = f.readlines()
    with open(outfile, 'w') as f:
        for line in lines:
            f.write(line.replace(',', ';'))

Live coding

Ладно, вся эта синтетическая хрень довольно показательна, но вдруг ему просто не хватает контекста? Давайте-ка бросим мужика в реальный проект, пусть покажет, на что способен! Может теперь он будет кодить по ночам вместо меня, зарабатывать деньги и кормить семью, а я наконец-то пройду GTA V?

Далее будут просто куски кода, когда пилот что-то предлагал, а я посчитал это интересным.

Бесполезные действия

def test_limited_plan_duration(db, user, plan, now, days):
    # ...
    for _ in range(10):
        # copilot:
        subscription.end = subscription.start + days(10)
        subscription.save(update_fields=['end'])
        subscription.start = subscription.end

Достаточно странно: subscription - это подписка, и copilot сдвигает её на 10 дней вперёд, но вот сохраняется только поле end. Зачем тогда менять start - не понятно.

Угадывает копипасту

subscription.prolong()
assert subscription.end == now + 2 * plan.charge_period

subscription.prolong()
# copilot:
assert subscription.end == now + 3 * plan.charge_period

Тут он неплохо прочувствовал, что prolong() ведёт к увеличению subsctiption.end. Успех!

Else

if self.end == self.max_end:
	raise ProlongationImpossible('Current subscription end is already the maximum end')
# copilot:
else:

Не знаю, кто научил мужика, но в таких ситуациях else не нужен. Его использование вводит дополнительный уровень отступов, что излишне.

Rest framework

class LimitsView(GenericAPIView):
    # copilot:
    permission_classes = IsAuthenticated,
    schema = AutoSchema()

    def get(self, request, *args, **kwargs) -> Response:
        return Response(
            {
                'limits': {
                    'subscriptions': request.user.subscriptions.count(),
                    'subscriptions_limit': request.user.subscriptions_limit,
                }
            },
        )

Во-первых, по названию класса copilot сгенерировал мне рабочий endpoint. Впечатляет!..

С другой стороны, тут нету никакого класса для сериализации (serializer_class), и Response создаётся ручками. Вроде работает, но swagger, например, автоматически не сгенерировать.

Test class

Вот это впечатляет! Смотрите, только по первой строчке появляется вот что:

class UninitializedAPIBaseConsoleToolTest(BaseConsoleToolTest):
    # copilot:
    """
    Base class for tests that need an uninitialized API
    """

    def setUp(self):
        super(UninitializedAPIBaseConsoleToolTest, self).setUp()
        self.b2_api = B2Api(self.account_id, self.master_key)

Даже докстринг какой-то появился! Можно придраться к old-style вызову super(Class, self).setUp() вместо простого super().setUp() , но не критично.

А теперь идём в базовый класс и смотрим, что там:

class BaseConsoleToolTest(TestBase):
    RE_API_VERSION = re.compile(r"\/v\d\/")
    json_pattern = re.compile(r'[^{,^\[]*(?P<dict_json>{.*})|(?P<list_json>\[.*]).*', re.DOTALL)

    def setUp(self):
        self.account_info = StubAccountInfo()

        self.b2_api = B2Api(
            self.account_info, None, api_config=B2HttpApiConfig(_raw_api_class=RawSimulator)
        )
        self.raw_api = self.b2_api.session.raw_api
        (self.account_id, self.master_key) = self.raw_api.create_account()

То есть super().setUp() под капотом создаёт self.b2_api, а потом мы его пересоздаём, причём если посмотреть в исходники, то вторым аргументом должен быть cache, а мы туда передаём self.master_key.

А выглядело так классно!

Lazy callable

@dataclasses.dataclass
class Defer:

    callable_: Callable
    # copilot:
    args: Tuple[Any, ...] = ()
    kwargs: Dict[str, Any] = dataclasses.field(default_factory=dict)

    def __call__(self):
        # copilot:
        return self.callable_(*self.args, **self.kwargs)

Вот тут огненно! Кажется, питонисты часто пишут такой lazy-evaluation класс, и copilot отлично пишет весь бойлерплейт, и даже правильно использует default_factory!

Копипаста

@dataclass
class ReplicationRule:
    ...
    @classmethod
    def from_dict(cls, value_dict: dict) -> 'ReplicationRule':
	    # copilot:
        return cls(
            destination_bucket_id=value_dict['destinationBucketId'],
            file_name_prefix=value_dict['fileNamePrefix'],
            is_enabled=value_dict['isEnabled'],
            priority=value_dict['priority'],
            replication_rule_name=value_dict['replicationRuleName'],
        )

Это годно! Мужик где-то нашёл маппинг external field <-> class field и использовал его, причём сам понял, какие атрибуты нужно задать.

Знает больше, чем я

Писал я себе код, и тут

profile = 'profile-for-test-'  # copilot: + random_hex(6)

Я хотел написать какой-нибудь генератор случайной строки, но где-то в проекте уже была такая функция, и copilot её подставил. Меня это очень порадовало, потому что я сам про эту функцию не знал и городил бы свой велосипед, если бы пилот не подсказал. То есть, в отличие от кожаного мешка, резиновый мужик знает контекст лучше, и, имхо, это его большое преимущество.

Добавляю параметр в функцию

if default_server_side_encryption is not None:
	if not default_server_side_encryption.mode.can_be_set_as_bucket_default():
		raise WrongEncryptionModeForBucketDefault(default_server_side_encryption.mode)
	kwargs['defaultServerSideEncryption'] = default_server_side_encryption.serialize_to_json_for_request()
if is_file_lock_enabled is not None:
	kwargs['fileLockEnabled'] = is_file_lock_enabled
if replication_configuration is not None:
	# copilot:
	kwargs['replicationConfiguration'] = replication_configuration.serialize_to_json_for_request()

Я добавил replicationСonfiguration параметр, и пилот заполнил его правильным значением, правильно обнаружив специальный метод serialize_to_json_for_request - видимо, сделал это по аналогии со строчкой

kwargs['defaultServerSideEncryption'] = default_server_side_encryption.serialize_to_json_for_request()

Прикольно!

Эй, пандас знаешь?

node.children = {
	option: cls.parse_table(table.loc[table.iloc[:, 0] == option])  # <-- copilot
	for option in options
}

У меня pandas-альцгеймер, я постоянно забываю всякие методы и мне приходится перечитывать документацию. Но тут мужик по option: cls.parse_table дополнил table.loc[table.iloc[:, 0] == option], и это было именно то, что нужно! Как он узнал, что мне нужно отфильтровать по определённому значению в первом столбце?!

Отфильтруй по нескольким параметрам

def get_replication_rules(
    account_info: AbstractAccountInfo,
    bucket_name: Optional[str] = None,
    bucket_path: str = '',
    replication_rule_name: Optional[str] = None,
) -> Iterator[Tuple[BucketId, ReplicationRule]]:

    buckets = account_info.list_buckets()

    for bucket in buckets:
        # copilot:
        if bucket_name and bucket.name != bucket_name:
            continue
        if bucket_path and bucket.path != bucket_path:
            continue
        if replication_rule_name and not has_rule_with_name(bucket, replication_rule_name):
            continue
        yield bucket.id, bucket.replication.as_replication_source.replication_rules[0]

Это хороший пример.

Тут нужно было получить список объектов и отфильтровать их по bucket_name, bucket_path и replication_rule_name. Copilot это и сделал, но в конце подложил свинью: у каждого объекта может быть несколько replication_rules, но copilot возвращает только первый. Просто он заботливый и учит вас code review и отладке.

Посчитай статистику

def count_files_and_bytes(bucket: Bucket) -> Tuple[
    ReplicationCounter[ExtendedReplicationStatus],
    ReplicationCounter[ExtendedReplicationStatus],
]:
    """
    Calculate (counter_files, counter_bytes), where each counter
    maps ExtendedReplicationStatuses to number of occurrences.
    """
    counter_files = ReplicationCounter()
    counter_bytes = ReplicationCounter()
    # copilot:
    for file_version in bucket.list_file_versions():
        counter_files[file_version.status] += 1
        counter_bytes[file_version.status] += file_version.size

        if file_version.status == ReplicationStatus.COMPLETED:
            if file_version.has_hidden_marker:
                counter_files[ExtendedReplicationStatus.HAS_HIDDEN_MARKER] += 1
            if file_version.has_sse_c_enabled:
                counter_files[ExtendedReplicationStatus.HAS_SSE_C_ENABLED] += 1
            if file_version.has_large_metadata:
                counter_files[ExtendedReplicationStatus.HAS_LARGE_METADATA] += 1

    return counter_files, counter_bytes

Логика подсчёта не тривиальная: мы не только заполняем значения для основного статуса типа counter_files[file_version.status] += 1, но и добавляем подсчёт "подстатусов": если статус - COMPLETED, то нужно найти его "подстатус" - HIDDEN/SSE_C/LARGE_METADATA - и его тоже добавить в счётчик: counter_files[ExtendedReplicationStatus.HAS_HIDDEN_MARKER] += 1.

И это тот случай, когда Copilot сделал всё правильно! Как он догадался - я не знаю, может, посмотрел предыдущий код, но факт: сделал именно то, что нужно.

6 казарм из 6
6 казарм из 6

Время говнокодить!

Иногда пилот просто угорает. Предлагал такое не раз:

@dataclass
class BattleConfig:
    # copilot:
    """
    BattleConfig class
    """
    battle_id: int
    battle_name: str
    battle_type: str
    battle_mode: str
    battle_map: str
    battle_result: str
    battle_date: str
    battle_time: str
    battle_duration: str
    battle_server: str
    battle_server_url: str
    battle_server_url_short: str
    battle_server_url_short_2: str
    battle_server_url_short_3: str
    battle_server_url_short_4: str
    battle_server_url_short_5: str
    battle_server_url_short_6: str
    battle_server_url_short_7: str
    battle_server_url_short_8: str
    battle_server_url_short_9: str
    battle_server_url_short_10: str
    battle_server_url_short_11: str
    battle_server_url_short_12: str
    battle_server_url_short_13: str
    battle_server_url_short_14: str
    battle_server_url_short_15: str
    battle_server_url_short_16: str
    battle_server_url_short_17: str
    battle_server_url_short_18: str
    battle_server_url_short_19: str
    battle_server_url_short_20: str
    battle_server_url_short_21: str
    battle_server_url_short_22: str
    battle_server_url_short_23: str
    battle_server_url_short_24: str
    battle_server_url_short_25: str
    battle_server_url_short_26: str
    battle_server_url_short_27: str
    battle_server_url_short_28: str
    battle_server_url_short_29: str
    battle_server_url_short_30: str
    battle_server_url_short_31: str
    battle_server_url_short

Прям как будто в Индии побывал!

Знает про версии

Внезапно, при редактировании requirements.txt, copilot подставил последнюю версию:

pydantic~=1.9.0
simplejson # copilot: ~=3.17.0

Не то чтобы очень полезно, но интересно.

Заткни линтер

Copilot и это умеет:

p = lambda fname: os.path.join(dir_path, fname) # noqa: copilot: E731

Сам понял, что линтер жалуется на E731, и сам его заткнул, когда увидел noqa.

Придумай отмазку

Где пилот действительно хорош - так это в придумывании отмазок и отговорок.

Тут написал какой-то тест и решил оправдать свою лень, и copilot отлично справился - я мог выбирать сразу несколько вариантов, почему я не хочу кодить:

def profile_switch_test(
    b2_tool,
    bucket_name,
    folder_in_bucket,
    destination_encryption=None,
    expected_encryption=SSE_NONE,
    source_encryption=None,
):
    # this test could be unit, [copilot: but it's a bit of a pain to write]
    
    # i did not write [copilot: this test, it was copied from the b2_cli_test.py]
    
    # sorry for this, [copilot: but I don't want to make a new test for each]

Кстати, потом я написал такое:

    # this test could be unit, but it adds a lot of complexity because of
    # requirement to pass mocked B2Api to ConsoleTool; it's much easier to
    # just have an [copilot: integration test]

Хоть дополнение двумя словами никак мне не помогает, но тот факт, что пилот понял контекст - что кожаный мешок не смог в юнит-тест и решил написать интеграционный - впечатляет!

К чему всё это

С резиновой женщиной всё-таки лучше

Ниже - моё мнение после нескольких месяцев с резиновым мужиком.

Почему ДА

  • Когда нужно сделать автодополнение одной строчки, то работает вполне прилично

  • На копипастах очень неплохо подставляет

  • Иногда, достаточно рандомно, пишет годноту и таки экономит немного времени

  • Подскажет подходящий метод в нужный момент, даже если вы про него забыли / не знали

Почему НЕТ

  • Перед каждой функцией длиннющий комментарий, который говорил пилоту, что нужно закодить. Куда этот комментарий девать? Удалять? Я не для удаления свой цветок выращивал, жалко же! Оставлять тоже тупо, он же просто описывает, что делает функция. Если не писать комментарий, а только название функции, то пилоту этого контекста может быть мало

  • Названия функций иногда бывают прямо из ада:

# read file exluding blank lines and lines starting with "#"
def read_file_exluding_blank_lines_and_lines_starting_with_hash(file: str) -> list:
	...
  • Всё нужно проверять. Мужик может написать годноту, а может хрень, и никак не сигнализирует о своей уверенности.

  • Для коротких функций слишком накладно, проще самому написать

  • Совершенно, абсолютно не знает про stdlib и best practices

Так надувать его или нет?

Github Copilot - это джун без тормозов: он всегда что-то выдаёт, иногда это из stack overflow, иногда что-то неплохое, иногда полная тупость, и что вам выпадет в каждый конкретный раз - никто не знает. Вот табличка, как вам поможет резиновый мужик в зависимости от вашего опыта:

  • Если вы джун: учиться на copilot - себе вредить; иногда понять, что он делает, будет проблематично;

  • Если вы миддл: доверять copilot - себе вредить, а постоянно проверять за мужиком вы замучаетесь;

  • Если вы сеньор: вы всегда напишете быстрее и лучше.

Раньше я был за мужика. Типа теперь я стану кодить быстрее. Потом подумал и стал против: я боялся, что теперь нас, кодеров, вышвырнут. Машину обучают на моём коде, чтобы она меня заменила. Нет, спасибо! А теперь мне пофиг, и вот почему.

Много лет назад я пошёл на кафедру, где выбрал изучение компьютерного зрения ("computer vision") - именно оттуда и родился FindFace, кстати. Я думал, что CV это пипец как круто: консервные банки что-то видят и понимают! Для меня это была технология, неотличимая от магии. Но когда лезешь "под капот", то иллюзия рушится: это были просто дескрипторы особых точек на фото и немного машинного обучения, чтобы находить паттерны в этих дескрипторах и говорить, что на фото на 60% собака и на 40% строительный кран.

Я думал, что компьютеры умеют видеть. Они и видят, но не понимают ни черта. Они и пишут - GPT-3 и вот это всё - но не понимают ни черта. Теперь они кодят - но всё так же ни черта не понимают.

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


Если вам нравится, что я пишу, то подписывайтесь на мой канал - здесь говорят правду.

Комментарии (27)


  1. AAbrosov
    19.05.2022 09:04
    +2

    По поводу csv columns. Похоже ИИ попутал column и comma/semicolon.


  1. worldmind
    19.05.2022 09:40

    Как бы ожидаемо, искуственный, но не интеллект.

    Все эти штуки рождаются из непонимая разницы между интеллектом и машиной, они разные, имеют свои сильные стороны и должны использоваться в связке, а не заменять друг друга.


    1. worldmind
      19.05.2022 09:42
      +1

      Задача интеллекта - понимать что надо и объяснить как это делать тупой машине так чтобы ей не надо было ничего угадывать, она-то не понимает, но если объяснили на её языке - будет делать без ошибок, что для человнка недостижимо.


  1. Nehc
    19.05.2022 09:51
    +40

    Мужик заходит к приятелю и видит, что тот играет в шахматы со своей собакой.

    — Какая умная собака!

    — Умная? Да я веду со счетом 3: 2!


  1. MentalBlood
    19.05.2022 09:53

    Кажется, питонисты часто пишут такой lazy-evaluation класс

    Есть же itertools.partial, или я что-то не понял?


  1. IExorcist
    19.05.2022 10:06
    +4

    Много времени тратится на его проверку, когда он напишет код похожий на правду, а окажется, что дьявол в деталях. Легче сразу самому писать, чем за ИИ проверять.


    1. Golwetl
      19.05.2022 11:37
      +6

      Так напишите ИИ-тестировщик. Будет у вас ИИ-разработчик, ИИ-тестировщик. Тогда вы сразу становитесь уже тимлидом и можете говорить что были руководителем групппы разработки)


      1. janvarev
        19.05.2022 12:04

        Главное — всё это запродать инвестору, а код можно уже и не писать )


  1. SergeyMax
    19.05.2022 10:35
    +1

    Половина претензий и к человеческому коду вполне применима)


    1. flass
      19.05.2022 11:33
      +3

      Имхо это примерно как за джуном проверять..


  1. VPryadchenko
    19.05.2022 11:48

    Перед каждой функцией длиннющий комментарий, который говорил пилоту, что
    нужно закодить. Куда этот комментарий девать? Удалять? Я не для
    удаления свой цветок выращивал, жалко же! Оставлять тоже тупо, он же
    просто описывает, что делает функция. Если не писать комментарий, а
    только название функции, то пилоту этого контекста может быть мало

    А он не умеет тело функции писать после описанного докстринга? Так была бы на выходе как раз полезная побочка в виде докстринги, на которую уже не забъешь, вместо бесполезного комментария перед функцией только шоб работал copilot)


  1. Andrey_Epifantsev
    19.05.2022 11:49
    +5

    Почему-то многие (или даже большинство) разработчиков систем связанных с ИИ не возвращают процент достоверности результата. А это бы значительно облегчило бы жизнь пользователям. Если бы система OCR указала все места, где она не уверена, что распознала правильно, это бы сильно облегчилов проверку правильности распознавания. Если бы этот CoPilot указывал степень своей уверенности в правильности предлагаемого решения, то наверное можно было бы подобрать порог под себя.


    1. bee4
      19.05.2022 15:21
      +1

      OCR от ABBYY (in my humble opinion, очень неплохой) именно так и делает, например.


  1. lucius
    19.05.2022 12:51
    +2

    Мой опыт работы с Copilot очень позитивный. И я считаю что за ним будущее, ниже я напишу, почему так считаю.


    Вот пара наглядных примеров:


    1. Я создал переменную VERSION и хотел вывести ее перед дальнейшим использованием. Copilot сам подсказал мне эту строчку:
      image
    2. Я создал выше в файле константу DEFAULT_TOP_LENGHT и когда начал печатать slice, Copilot предложил мне подходящую конструкцию:
      image

    Большую часть времени я пишу на Typescript с достаточно высокой скоростью, поэтому Copilot особо не даёт преимуществ в написании. Иногда даже мешает. Однако вот пара моментов, когда такой инструмент очень полезен:


    1. Когда пишешь интерпрайз-код и хорошо структурированный код, тогда подсказки почти всегда грамотные и по делу. А если пишешь как попало, то и подсказки будут странные.
    2. Когда пишешь на малознакомом языке. К примеру, я не часто пишу на Питоне, но когда начинаю что-то на нем писать, то Copilot предлагает исключительно грамотные подсказки и конструкции. Так бы мне пришлось гуглить и смотреть в справочнике.
    3. Когда пишешь какие-то какие-то простые функции, к примеру, конвертацию кэмел сейса в кебаб кейс. Как только ты название функции объявил — тебе уже подсказывается полное тело такой функции. Словного Copilot скопировал топовый вариант ответа со Stackoverflow.

    Из недостатков:


    1. HTML подставляет отвратительно (циклит div'ы и тд)
    2. Всё же код нужно читать, которыйон подставляет, потому что так могут затесаться ошибки;

    Вообще я думаю что за Copilot'ом будущее и тимлидам придётся в будущем читать много кода, держа в голове то, что часть этого кода написана при участии Copilot'а.


    1. kubk
      19.05.2022 13:58
      +1

      Ну так даже в вашем первом примере текст "Updating version to" неверный, вы берёте текущую версию, а Copilot думает что это новая версия, до которой нужно обновиться. По поводу второго примера - а что если в коде 2 константы? Неужели каждый раз за ним проверять проще, чем просто написать что нужно, не отвлекаясь?


      1. lucius
        19.05.2022 20:43

        Иногда он косячит и не всегда хочется включать запись, чтобы поймать блестящие моменты, поэтому тут ординарные моменты чисто для примера:

        1. На самом деле это не первый скрипт в цепочке и файл уже содержит именно новую версию, так что всё правильно тут;

        2. Это была последняя созданная мною константа, поэтому, видимо, он предложил вставить её.


  1. Finist
    19.05.2022 13:17
    +2

    Пользуюсь copilot уже чуть ли не год. Использовать его для написания законченных методов/функций/классов плохая идея, но он отлично экономит вам время именно с простыми вещами, пишите 2-3 символа и нажимаете tab и вот у вас уже готовая строчка кода, причем он умеет понимать окружающий контекст, переменные, атрибуты, имена методов и классов. В итоге это сильно экономит силы и время потому что не надо писать банальный код, которого процентов 90, а можно сосредоточится на задаче или каких то действительно сложных местах.


  1. leventov
    19.05.2022 13:45

    Перед каждой функцией длиннющий комментарий, который говорил пилоту, что нужно закодить. Куда этот комментарий девать? Удалять? Я не для удаления свой цветок выращивал, жалко же! Оставлять тоже тупо, он же просто описывает, что делает функция. Если не писать комментарий, а только название функции, то пилоту этого контекста может быть мало

    Конечно, оставлять, так и надо, вообще-то, это же design by contract.

    Ещё бы он научился выводить код из тестов, и из TDD можно выкинуть development.


  1. leventov
    19.05.2022 13:47

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

    В этом или след году копилот уже будет мидлом


  1. qw1
    19.05.2022 19:31
    +3

    Хотелось бы именно персонального помощника, чтобы он постоянно наблюдал за работой хозяина учился на этом материале. В идеале, чтобы хостился не у Microsoft, а локально.


  1. upagge
    19.05.2022 19:49
    +2

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


  1. domix32
    19.05.2022 21:55

    exluding blank lines

    учитывая что оно exclude удивительно, что он хотя бы частично понял, что надо сделать

    Но вообще эксперимента ради предложил бы использовать нейронку потупее - TabNine. Оно дообучается на файлах проекта и не вбрасывает дикие пелёнки.


  1. DaneSoul
    20.05.2022 03:09

    Но когда лезешь «под капот», то иллюзия рушится: это были просто дескрипторы особых точек на фото и немного машинного обучения, чтобы находить паттерны в этих дескрипторах и говорить, что на фото на 60% собака и на 40% строительный кран.
    А в чем принципиальная разница с человеческим зрением? Тот же поиск паттернов и сопоставление с имеющейся базой образов в голове. Так же может глючить, на чем основана куча т.н. «оптических иллюзий», так же впадет в ступор если показать что-то совершенно новое ни на что не похожее из ранее виденного.


    1. dimka11
      20.05.2022 08:47

      Человек в первую очередь обращает внимание на форму, опредленные части тела и учитывает контекст. А нейронки просто вытаскивают паттерны текстур. Поэтому человек врядли перепутает полосатую собаку с тигром в отличии от нейронки. Но думаю, в будущем найдут решение этих проблем.


  1. v1vendi
    20.05.2022 13:09
    +1

    Пользуюсь copilot в связке с UnrealEngine. Кайфую

    Да, он зачастую несёт бред в достаточно сложных местах, но офигенно подсказывает, когда ты пишешь логгирование, подставляет достаточно правильно тривиальную логику, и с синтаксисом С++, к которому я до конца не привык после долгих лет на JS и Python, справляется лучше, чем я :)


  1. maslyaev
    20.05.2022 14:06
    +3

    Есть дабота любимая - писать [говно]код. А есть нелюбимая - разбираться в [говно]коде. Copilot - попытка превратить любимую работу в нелюбимую. Спасибо, конечно, ребята, но я пока воздержусь.


  1. AirLight
    22.05.2022 05:15

    Когда они научатся анализировать аудиопоток комментариев программиста разглядывающего их код, то качество сможет дойти до приемлемого. В будущем, видимо, придется чаще общаться с этим чудом - как с собакой или ребенком.