Czy istnieją powszechnie akceptowane wytyczne dotyczące pisania nowoczesnego języka C?

13

Mam silne doświadczenie w Javie / Groovy i zostałem przydzielony do zespołu, który utrzymuje dość dużą bazę kodu C dla oprogramowania administracyjnego.

Niektóre problemy, takie jak radzenie sobie z kroplami w bazie danych lub generowanie raportów w plikach PDF i Excel, zostały przeniesione na zewnętrzną usługę Java.

Jednak jako programista Java jestem nieco zdezorientowany niektórymi aspektami kodu:

  • jest pełny (szczególnie w przypadku „wyjątku”)
  • istnieje wiele ogromnych metod (wiele ponad 2000 metod linii)
  • nie ma zaawansowanych struktur danych (bardzo tęsknię za Listą, Setem i Mapą)
  • brak rozdzielenia problemów (SQL jest z radością mieszany w całym kodzie)

W rezultacie czuję, że biznes jest ukryty w tonach kodu technicznego, a mój mózg, w kształcie Object Oriented i szczypta programowania funkcjonalnego, nie jest spokojny.

Dobrą stroną projektu jest to, że kod jest prosty: nie ma frameworka, nie ma manipulacji kodem bajtowym w czasie wykonywania, nie ma AOP. A serwer może jednocześnie odpowiadać ponad 10000 użytkownikom za pomocą jednego komputera, zużywając mniej pamięci niż java musi wypluć „witaj świecie”.

Chcę nauczyć się pisać kod C zgodnie z powszechnie przyjętymi nowoczesnymi zasadami. Czy istnieją powszechnie akceptowane zasady dotyczące pisania i struktury nowoczesnego języka C?

Coś trochę jak odpowiednik książki „Effective Java”, ale dla C.

Edytuj w świetle odpowiedzi i komentarzy:

  • Spróbuję dostosować mój sposób myślenia do kodu C i nie będę próbował kopiować go do OOP.
  • Zacząłem czytać i czytać zalecane przewodniki po stylach kodowania z komentarza (Standardy kodowania GNU i Styl kodowania jądra systemu Linux).
  • Spróbuję następnie zaproponować ten styl kodu moim współpracownikom. Najtrudniejszą częścią może być przekonanie współpracowników, że ogromną metodę można podzielić na mniejsze części i że powtórzenie tych samych 4 wierszy kodu obsługi błędów można uniknąć za pomocą metody.
Guillaume
źródło
5
Czy aplikacja rzeczywiście wymaga modernizacji, czy uważasz, że tak, ponieważ sposób jej napisania jest nieznany?
Blrfl,
1
@Blrfl, czuję, że aplikacja została napisana z nieaktualnym standardem. Chcę tylko wiedzieć, jaki jest dzisiaj (2016) standard dla (administracyjnego) C. Jeśli taki istnieje. Nie chcę refaktoryzować ani zmieniać kształtu bieżącej aplikacji, chcę mieć pomysł, jak napisać następną część kodu.
Guillaume,
3
@antlersoft: Funkcja 2000 linii, która wykonuje długą listę prostych rzeczy jedna po drugiej, absolutnie nie stanowi problemu i nie potrzebuje wymówki. Proszę nie odpowiadać okrągłymi argumentami, takimi jak: „nie powinieneś pisać 2000 funkcji liniowych, ponieważ nie powinieneś pisać 2000 funkcji liniowych”.
gnasher729,

Odpowiedzi:

14

Mogę odczytać z twojego pytania, że ​​problemem nie jest to, że kod jest stary C, ale po prostu złe programowanie. Większość problemów, o których wspominałeś, takich jak gadatliwość, ogromne funkcje linii 2000+ lub brak rozdzielenia problemów dotyczy wszystkich języków, C i Javy.

O szczegółowości wspomniano w kontekście obsługi błędów. Nie podałeś żadnego przykładu, więc mogę jedynie przypomnieć, że kod obsługi błędów to także kod . Nie ma usprawiedliwienia dla powtarzających się fragmentów kodu płyty kotłowej. Rozłóż to; albo do funkcji, albo (jeśli nie warto tworzyć osobnej funkcji) wykonaj goto Error;wzorzec i przenieś obsługę błędów i czyszczenie zasobów do Error:sekcji na dole funkcji.

Jeśli przekazanie błędu w górę łańcucha połączeń wydaje się być problemem, zadaj sobie pytanie: czy funkcja tam naprawdę musi wiedzieć, że jakiś mały facet tutaj miał problem? Mechanizmy wyjątków wbudowane w język ułatwiają to, ale generalnie lepiej jest obsługiwać wyjątki wcześniej (w dowolnym języku), aby warunek błędu nie zanieczyszczał logiki kodu wysokiego poziomu. A jeśli funkcja tam naprawdę musi wiedzieć, istnieją sposoby naśladowania wyjątków za pomocą setjmpi longjmp.

Myślę, że jedynym wspomnianym problemem związanym z C jest brak standardowych pojemników. Chociaż Setogólnie można go zastąpić posortowaną tablicą i Map(w przeważającej części) tablicą par lub struct(jeśli znasz zestaw kluczy, map[key] = valuezmienia się w s.key = value), ale faktem jest, że nie ma dynamicznego kontenera tablic w standardzie biblioteka. W C99 możesz przynajmniej zadeklarować tablicę o zmiennej długości na stosie ( int array[len]), ale musisz lenwcześniej obliczyć (zwykle nie trudne) i oczywiście nie możesz zwrócić jej jako dowolnego obiektu przydzielonego do stosu. Większość projektów kończy się pisaniem własnego kontenera z dynamiczną tablicą lub przyjmowaniem kontenera typu open source.

Na zakończenie chciałbym zaznaczyć, że tam byłem. Byłem programistą Java, który przeniósł się do C ++ i czystego C. Chciałbym poradzić „przeczytaj książkę X, aby nauczyć się dobrego C”, ale nie ma czegoś takiego jak Java. Drogą do przodu jest wchłonięcie wszystkich zawiłości języka i standardowej biblioteki; google dużo, dużo czytam, a kod sporo aż zaczniesz myśleć w C. próbuje pisać rzeczy w C, tak jak w Javie jest tak frustrujące, jak próbuje napisać zdanie w języku obcym ze słowami bezpośrednio tłumaczone z matką język; zarówno ty, jak i czytelnik skulicie się. Dobra wiadomość jest taka, że ​​nauka dobrego programowania jest powolna, ale nauka innego języka jest szybka. więc jeśli napiszesz porządny kod w Javie,

Sowa
źródło
1
Podsumowując, to naprawdę dobra odpowiedź. Chciałbym tylko sprzeciwić się postrzeganiu setjmp()/ longjmp()jako ważnego narzędzia: nawet nie próbuje wykonać żadnego czyszczenia. Wszelkie alokacje zostaną ujawnione, blokada wstrzymana nie zostanie zwolniona, żaden otwarty plik nie zostanie zamknięty, a wszelkie przejściowe niespójności danych staną się trwałe. IMHO, ta para funkcji jest w zasadzie najgorszym hakiem, jaki kiedykolwiek wymyślono, z jedynym uzasadnieniem, że można ją wdrożyć. W końcu istnieje tylko jeden prawidłowy sposób obsługi błędów w C: jawne kody błędów.
cmaster
@cmaster tak. Osobiście setjmp/longjmpwyglądam jak ryba z wody w C i nigdy ich nie użyłem. Czułem się zmuszony do włączenia ich tylko z powodu licznych samouczków / bibliotek w Internecie naśladujących wyjątki, więc pomyślałem, że są ludzie, którzy faktycznie z niego korzystają.
Sowa
7

Dobrą stroną projektu jest to, że kod jest prosty: nie ma frameworka, nie ma manipulacji kodem bajtowym w czasie wykonywania, nie ma AOP. A serwer może jednocześnie odpowiadać ponad 10000 użytkownikom za pomocą jednego komputera, zużywając mniej pamięci niż java musi wypluć „witaj świecie”.

Polecam ostrożność, czy warto poświęcić swój czas i pieniądze firmy, by wydać zasoby na „modernizację” działającego oprogramowania o niskiej złożoności kodu i który osiąga dobre wyniki. Istnieje duże prawdopodobieństwo, że sam wprowadzisz nowe błędy, zwłaszcza że wydaje się, że jest to system, którego nie znasz.

Jeśli nadal chcesz zejść tą drogą, proponuję następujące:

  • Utwórz (lub wygeneruj) diagram stanu oprogramowania / kodu
  • Zanurz się w kodzie i zrób listę najbardziej skomplikowanych lub krytycznych części kodu
  • Znajdź kogoś, kto ma wiedzę na temat tej bazy kodu i zapytaj ją, dlaczego została zbudowana w ten sposób i co powoduje problemy
  • Napisz dokumentację z tego, czego się nauczyłeś

W tym momencie zdecydujesz, czy warto to zbadać. Jeśli kultura Twojej firmy nie nagradza porażki, zdobądź zielone światło od wyższej osoby lub od menedżera.

  • Podziel na segmenty różnych elementów składowych oprogramowania i napisz testy jednostkowe dla każdego z nich.
  • Iteruj, aż będziesz mógł skleić różne moduły razem
  • Wykonuj dalsze testy symulujące rzeczywistą interakcję użytkownika (testy warunków skrajnych itp.)

Myślę, że to dość dobra mapa drogowa i zabierze Cię tam, gdzie potrzebujesz. Bez znajomości specyfiki tego projektu trudno ci bardzo pomóc. Proszę nie odrzucać mojego wyłączenia odpowiedzialności jako nadmiernie alarmującego. Mnóstwo doskonałych programistów pobiło proch, próbując przepisać istniejący projekt na swój ulubiony język lub używając „nowoczesnych” narzędzi. Jest to decyzja, którą należy dokładnie przemyśleć i wzywam was, abyście nie oszukali się i nie zrobili tego samodzielnie bez wsparcia kierownictwa lub pomocy ze strony swoich kolegów.

Jerry Dean
źródło
2
Rozumiem, że moje pytanie wcale nie było jasne. Nie chcę refaktoryzować kodu. W ogóle. Chcę zachować istniejącą bazę kodu taką, jaka jest. Chcę jednak nauczyć się pisać nowoczesny C dla nowej funkcji. I tu się zgubiłem. Większość dokumentacji, którą znalazłem, dotyczy kodowania w C, a nie pisania „modern” C. Może nie ma czegoś takiego jak „modern” C ...
Guillaume,
1

Jeśli wolisz język wyższego poziomu, istnieje kilka języków, takich jak C ++ lub Objective-C, które można łatwo mieszać z kodem C.

Alternatywnie, C i C ++ są w miarę kompatybilne. Być może będziesz w stanie skompilować całą bazę kodu jako C ++ z kilkoma zmianami - będziesz mieć od czasu do czasu zmienną o nazwie „klasa” lub „szablon”, której nazwę musisz zmienić, ale w praktyce to wszystko. (sizeof („a”) jest inny w C i C ++, ale nie sądzę, żebym kiedykolwiek tego używał).

Jeśli pójdziesz tą drogą, weź pod uwagę, że następny opiekun może nie być zbyt biegły w C ++. Nie daj się ponieść emocjom. Skorzystaj z C ++, ale tylko do tego stopnia, że ​​programista C może go łatwo zrozumieć.

gnasher729
źródło
1
Nie mogę się tutaj zgodzić. C i C ++ są odrębnymi językami, a niektóre kody wymagane przez kompilator C ++ (jawnie rzutujące wartość zwracaną malloc) są uważane za złą praktykę w C. Znaczenie consti inlinejest również bardzo różne między C i C ++, i oczywiście C ++ nie rozumie __restrict. Nie traktuj języków jako wymiennych, nawet w podzbiorze źródeł, które kompilują się w obu.
Angew nie jest już dumny z SO
1

Zasadniczo pisanie dobrego kodu C jest tak samo jak pisanie dobrego kodu C ++ lub Java: Chcesz mieć klasę, użyj struct. Chcesz dziedziczenia, dołącz bazę structjako pierwszy bezimienny członek. Chcesz funkcji wirtualnych, dodaj wskaźnik do statycznego structwskaźnika funkcji. I tak dalej itd. To jest dokładnie to, co C ++ robi pod maską, jedyną różnicą jest to, że jest to jawne w C. W ten sposób możesz robić doskonale zorientowane obiektowo programowanie w C, wygląda to po prostu nieco inaczej i bardziej kocio niż na to, co robisz są używane do.

Chodzi o to, że dobre programowanie dotyczy paradygmatów, a nie funkcji językowych. To prawda, że ​​zawsze dobrze jest, jeśli funkcje językowe zapewniają dobre wsparcie dla paradygmatów, których chcesz użyć, ale funkcje językowe nie są wymagane. Kiedy sobie to uświadomisz, możesz napisać dobry kod w prawie dowolnym języku (poza niektórymi językami ezoterycznymi, takimi jak pieprzenie mózgu lub INTERCAL).

Oczywiście pozostaje problem, że standardowa biblioteka C nie zawiera żadnej z tych fajnych klas kontenerów, do których jesteś przyzwyczajony. Niestety oznacza to, że będziesz musiał albo użyć własnego, albo obejść ten brak, korzystając z dynamicznie przydzielanych tablic. Ale założę się, że wkrótce się przekonasz, że wszystko, czego naprawdę potrzebujesz, to dynamiczne tablice ( malloc()) i powiązane listy / drzewa, które są implementowane za pośrednictwem elementów wskaźnika w twoich klasach.

cmaster - przywróć monikę
źródło