Für Java-Programmierer gibt es nützliche Tools, mit denen Sie hochwertigen Code schreiben können, z. B. die leistungsstarke IntelliJ IDEA-Entwicklungsumgebung, kostenlose SpotBugs, PMD-Analysatoren und andere. All dies wird bereits in der Entwicklung des CUBA Platform-Projekts verwendet. In dieser Überprüfung der gefundenen Codefehler werde ich Ihnen erläutern, wie Sie die Qualität des Projekts mithilfe des statischen Code-Analysators PVS-Studio verbessern können.
Über das Projekt und den Analysator
Die CUBA-Plattform ist ein Java-Framework auf hoher Ebene zum schnellen Erstellen von Unternehmensanwendungen mit einer vollständigen Weboberfläche. Die Plattform abstrahiert den Entwickler von heterogenen Technologien, sodass Sie sich gleichzeitig auf die Lösung von Geschäftsproblemen konzentrieren können, ohne dass es unmöglich ist, direkt mit ihnen zu arbeiten. Der Quellcode wird aus dem Repository auf
GitHub übernommen .
PVS-Studio ist ein Tool zum Erkennen von Fehlern und potenziellen Schwachstellen im Quellcode von Programmen, die in C, C ++, C # und Java geschrieben wurden. Es funktioniert auf 64-Bit-Systemen unter Windows, Linux und MacOS. Für Java-Programmierer haben wir Plugins für Maven, Gradle und IntelliJ IDEA entwickelt. Das CUBA Platform-Projekt konnte mithilfe des Gradle-Plugins problemlos überprüft werden.
Fehler in den Bedingungen
Warnung 1V6007 Ausdruck 'StringUtils.isNotEmpty ("handleTabKey")' ist immer wahr. SourceCodeEditorLoader.java (60)
@Override public void loadComponent() { .... String handleTabKey = element.attributeValue("handleTabKey"); if (StringUtils.isNotEmpty("handleTabKey")) { resultComponent.setHandleTabKey(Boolean.parseBoolean(handleTabKey)); } .... }
Nach dem Abrufen des Attributwerts aus einem Element wird die Überprüfung dieses Werts nicht durchgeführt. Stattdessen wird eine konstante Zeichenfolge an die Funktion
isNotEmpty übergeben , aber wir mussten die Variable
handleTabKey übergeben .
Es gibt einen weiteren ähnlichen Fehler in der Datei AbstractTableLoader.java:
- V6007 Ausdruck 'StringUtils.isNotEmpty ("editierbar")' ist immer wahr. AbstractTableLoader.java (596)
Warnung 2V6007 Der Ausdruck 'previousMenuItemFlatIndex> = 0' ist immer wahr. CubaSideMenuWidget.java (328)
protected MenuItemWidget findNextMenuItem(MenuItemWidget currentItem) { List<MenuTreeNode> menuTree = buildVisibleTree(this); List<MenuItemWidget> menuItemWidgets = menuTreeToList(menuTree); int menuItemFlatIndex = menuItemWidgets.indexOf(currentItem); int previousMenuItemFlatIndex = menuItemFlatIndex + 1; if (previousMenuItemFlatIndex >= 0) { return menuItemWidgets.get(previousMenuItemFlatIndex); } return null; }
Die Funktion
indexOf kann
-1 zurückgeben, wenn das Element nicht in der Liste gefunden wird. Dann wird einer zum Index hinzugefügt, wodurch die Situation ausgeblendet wird, in der das gewünschte Element fehlt. Ein weiteres potenzielles Problem kann die Tatsache sein, dass die Variable
previousMenuItemFlatIndex immer größer oder gleich Null ist. Wenn beispielsweise die Liste
menuItemWidgets leer ist, kann die Grenze des Arrays überschritten werden.
Warnung 3V6009 Die Funktion 'Löschen' könnte den Wert '-1' empfangen, während ein nicht negativer Wert erwartet wird. Argument überprüfen: 1. AbstractCollectionDatasource.java (556)
protected DataLoadContextQuery createDataQuery(....) { .... StringBuilder orderBy = new StringBuilder(); .... if (orderBy.length() > 0) { orderBy.delete(orderBy.length() - 2, orderBy.length()); orderBy.insert(0, " order by "); } .... }
In der Zeichenpufferreihenfolge
By werden die letzten 2 Zeichen gelöscht, wenn ihre Gesamtzahl größer als Null ist, d.h. Eine Zeichenfolge enthält ein oder mehrere Zeichen. Die Startposition zum Löschen von Zeichen wurde jedoch mit einem Versatz von 2 Zeichen festgelegt. Wenn
orderBy plötzlich aus 1 Zeichen besteht,
löst ein Löschversuch eine
StringIndexOutOfBoundsException aus .
Warnung 4V6013 Die Objekte 'masterCollection' und 'entity' werden als Referenz verglichen. Möglicherweise war ein Gleichstellungsvergleich beabsichtigt. CollectionPropertyContainerImpl.java (81)
@Override public void setItems(@Nullable Collection<E> entities) { super.setItems(entities); Entity masterItem = master.getItemOrNull(); if (masterItem != null) { MetaProperty masterProperty = getMasterProperty(); Collection masterCollection = masterItem.getValue(masterProperty.getName()); if (masterCollection != entities) { updateMasterCollection(masterProperty, masterCollection, entities); } } }
In der Funktion
updateMasterCollection werden Werte von
Entitäten in
masterCollection kopiert. Zuvor wurden die Sammlungen anhand von Referenzen verglichen, aber möglicherweise war geplant, sie nach Wert zu vergleichen.
Warnung 5V6013 Die Objekte 'value' und 'oldValue' werden als Referenz verglichen. Möglicherweise war ein Gleichstellungsvergleich beabsichtigt. WebOptionsList.java (278)
protected boolean isCollectionValuesChanged(Collection<I> value, Collection<I> oldValue) { return value != oldValue; }
Dieses Beispiel ähnelt dem vorherigen. Hier ist
isCollectionValuesChanged zum Vergleichen von Sammlungen. Vielleicht ist ein Vergleich durch Bezugnahme auch nicht der erwartete Weg.
Überbedingungen
Warnung 1V6007 Ausdruck 'mask.charAt (i + offset)! = PlaceHolder' ist immer wahr. DatePickerDocument.java (238)
private String calculateFormattedString(int offset, String text) .... { .... if ((mask.charAt(i + offset) == placeHolder)) {
Im zweiten Vergleich wird ein Ausdruck geprüft, der dem ersten entgegengesetzt ist. Die zweite Prüfung kann entfernt werden, um den Code zu vereinfachen.
V6007 Der Ausdruck 'connector == null' ist immer falsch. HTML5Support.java (169)
private boolean validate(NativeEvent event) { .... while (connector == null) { widget = widget.getParent(); connector = Util.findConnectorFor(widget); } if (this.connector == connector) { return true; } else if (connector == null) {
Nach Abschluss der
while-Schleife ist der Wert der
Konnektorvariablen nicht
null , daher kann die redundante Prüfung entfernt werden.
Ein weiterer verdächtiger Ort, auf den Entwickler achten sollten:
- V6007 Ausdruck 'StringUtils.isBlank (strValue)' ist immer wahr. Param.java (818)
Nicht erreichbarer Code in Tests
V6019 Nicht erreichbarer Code erkannt. Möglicherweise liegt ein Fehler vor. TransactionTest.java (283)
private void throwException() { throw new RuntimeException(TEST_EXCEPTION_MSG); } @Test public void testSuspendRollback() { Transaction tx = cont.persistence().createTransaction(); try { .... Transaction tx1 = cont.persistence().createTransaction(); try { EntityManager em1 = cont.persistence().getEntityManager(); assertTrue(em != em1); Server server1 = em1.find(Server.class, server.getId()); assertNull(server1); throwException();
Die Funktion
throwException löst eine Ausnahme aus,
die den Aufruf der Funktion
tx1.commit verhindert. Vielleicht sollten diese Zeilen vertauscht werden.
Einige weitere ähnliche Stellen in anderen Tests:
- V6019 Nicht erreichbarer Code erkannt. Möglicherweise liegt ein Fehler vor. TransactionTest.java (218)
- V6019 Nicht erreichbarer Code erkannt. Möglicherweise liegt ein Fehler vor. TransactionTest.java (163)
- V6019 Nicht erreichbarer Code erkannt. Möglicherweise liegt ein Fehler vor. TransactionTest.java (203)
- V6019 Nicht erreichbarer Code erkannt. Möglicherweise liegt ein Fehler vor. TransactionTest.java (137)
- V6019 Nicht erreichbarer Code erkannt. Möglicherweise liegt ein Fehler vor. UpdateDetachedTest.java (153)
- V6019 Nicht erreichbarer Code erkannt. Möglicherweise liegt ein Fehler vor. EclipseLinkDetachedTest.java (132)
- V6019 Nicht erreichbarer Code erkannt. Möglicherweise liegt ein Fehler vor. PersistenceTest.java (223)
Verdächtige Funktionsargumente
Warnung 1V6023 Der Parameter 'salt' wird vor der Verwendung immer im Methodenkörper neu geschrieben. BCryptEncryptionModule.java (47)
@Override public String getHash(String content, String salt) { salt = BCrypt.gensalt(); return BCrypt.hashpw(content, salt); }
In der Kryptographie ist
Salt eine Datenfolge, die zusammen mit einem Kennwort an eine Hash-Funktion übergeben wird. Es wird hauptsächlich zum Schutz vor Wörterbuchsuchen und Angriffen mithilfe von Regenbogentabellen sowie zum Ausblenden derselben Kennwörter verwendet. Weiterlesen:
Salz (Kryptographie) .
In dieser Funktion wird die Linie sofort nach dem Aufrufen der Funktion gerieben. Möglicherweise ist das Ignorieren des übergebenen Werts eine potenzielle Sicherheitsanfälligkeit.
Warnung 2Für die betrachtete Funktion gibt der Analysator zwei Warnungen gleichzeitig aus:
- V6023 Der Parameter 'offsetWidth' wird vor seiner Verwendung immer im Methodenkörper neu geschrieben. CubaSuggestionFieldWidget.java (433)
- V6023 Der Parameter 'offsetHeight' wird vor seiner Verwendung immer im Methodenkörper neu geschrieben. CubaSuggestionFieldWidget.java (433)
@Override public void setPosition(int offsetWidth, int offsetHeight) { offsetHeight = getOffsetHeight(); .... if (offsetHeight + getPopupTop() > ....)) { .... } .... offsetWidth = containerFirstChild.getOffsetWidth(); if (offsetWidth + getPopupLeft() > ....)) { .... } else { left = getPopupLeft(); } setPopupPosition(left, top); }
Amüsanter Code. Die Funktion
akzeptiert nur zwei Variablen:
offsetWidth und
offsetHeight . Beide werden vor der Verwendung überschrieben.
Warnung 3V6022 Der Parameter 'Verknüpfung' wird im Konstruktorkörper nicht verwendet. DeclarativeTrackingAction.java (47)
public DeclarativeTrackingAction(String id, String caption, String description, String icon, String enable, String visible, String methodName, @Nullable String shortcut, ActionsHolder holder) { super(id); this.caption = caption; this.description = description; this.icon = icon; setEnabled(enable == null || Boolean.parseBoolean(enable)); setVisible(visible == null || Boolean.parseBoolean(visible)); this.methodName = methodName; checkActionsHolder(holder); }
Der Wert des
Shortcut- Parameters wird in der Funktion nicht verwendet. Die Funktionsoberfläche ist möglicherweise veraltet oder diese Warnung ist kein Fehler.
Noch ein paar ähnliche Orte:
- V6022 Der Parameter 'type' wird im Konstruktorkörper nicht verwendet. QueryNode.java (36)
- V6022 Der Parameter 'text2' wird im Konstruktorkörper nicht verwendet. MarkerAddition.java (22)
- V6022 Der Parameter 'Auswahl' wird im Konstruktorkörper nicht verwendet. AceEditor.java (114)
- V6022 Der Parameter 'options' wird im Konstruktorkörper nicht verwendet. EntitySerialization.java (379)
Verschiedene Funktionen mit dem gleichen Code
Warnung 1V6032 Es ist seltsam, dass der Hauptteil der Methode 'firstItemId' dem Hauptteil einer anderen Methode 'lastItemId' vollständig entspricht. ContainerTableItems.java (213), ContainerTableItems.java (219)
@Override public Object firstItemId() { List<E> items = container.getItems(); return items.isEmpty() ? null : items.get(0).getId(); } @Override public Object lastItemId() { List<E> items = container.getItems(); return items.isEmpty() ? null : items.get(0).getId(); }
Die Funktionen
firstItemId und
lastItemId haben dieselbe Implementierung. In letzterem Fall war es höchstwahrscheinlich erforderlich, ein Element nicht mit dem Index
0 zu erhalten , sondern den Index des letzten Elements zu berechnen.
Warnung 2V6032 Es ist seltsam, dass der Hauptteil der Methode dem Körper einer anderen Methode vollständig entspricht. SearchComboBoxPainter.java (495), SearchComboBoxPainter.java (501)
private void paintBackgroundDisabledAndEditable(Graphics2D g) { rect = decodeRect1(); g.setPaint(color53); g.fill(rect); } private void paintBackgroundEnabledAndEditable(Graphics2D g) { rect = decodeRect1(); g.setPaint(color53); g.fill(rect); }
Zwei weitere Funktionen mit verdächtig identischer Implementierung. Ich würde es wagen vorzuschlagen, dass in einem von ihnen eine andere Farbe als
color53 verwendet werden musste .
Nullreferenz
Warnung 1V6060 Die Referenz 'descriptionPopup' wurde verwendet, bevor sie gegen null verifiziert wurde. SuggestPopup.java (252), SuggestPopup.java (251)
protected void updateDescriptionPopupPosition() { int x = getAbsoluteLeft() + WIDTH; int y = getAbsoluteTop(); descriptionPopup.setPopupPosition(x, y); if (descriptionPopup!=null) { descriptionPopup.setPopupPosition(x, y); } }
In nur zwei Zeilen gelang es dem Autor, einen sehr verdächtigen Code zu schreiben. Zuerst wird die
setPopupPosition- Methode für das
descriptionPopup- Objekt aufgerufen, und dann wird das Objekt mit
null verglichen. Höchstwahrscheinlich ist der erste Aufruf der Funktion
setPopupPosition redundant und gefährlich. Es sieht nach den Folgen eines fehlgeschlagenen Refactorings aus.
Warnungen 2V6060 Die 'tableModel'-Referenz wurde verwendet, bevor sie gegen null verifiziert wurde. DesktopAbstractTable.java (1580), DesktopAbstractTable.java (1564)
protected Column addRuntimeGeneratedColumn(String columnId) {
Eine ähnliche Situation besteht bei dieser Funktion. Nach zahlreichen Aufrufen des
tableModel- Objekts wird
geprüft , ob es
null ist oder nicht.
Ein weiteres Beispiel:
- V6060 Die 'tableModel'-Referenz wurde verwendet, bevor sie gegen null verifiziert wurde. DesktopAbstractTable.java (596), DesktopAbstractTable.java (579)
Vielleicht ein logischer Fehler
V6026 Dieser Wert ist bereits der Variablen 'sortAscending' zugeordnet. CubaScrollTableWidget.java (488)
@Override protected void sortColumn() { .... if (sortAscending) { if (sortClickCounter < 2) {
In der ersten Bedingung ist die Variable
sortAscending bereits wahr , ihr wird jedoch immer noch der gleiche Wert zugewiesen. Vielleicht ist das ein Fehler und wollte
falsch zuweisen.
Ein ähnliches Beispiel aus einer anderen Datei:
- V6026 Dieser Wert ist bereits der Variablen 'sortAscending' zugeordnet. CubaTreeTableWidget.java (444)
Rückgabewerte für seltsame Funktionen
Warnung 1V6037 Eine bedingungslose 'Rückkehr' innerhalb einer Schleife. QueryCacheManager.java (128)
public <T> T getSingleResultFromCache(QueryKey queryKey, List<View> views) { .... for (Object id : queryResult.getResult()) { return (T) em.find(metaClass.getJavaClass(), id, views.toArray(....)); } .... }
Der Analysator hat bei der ersten Iteration der
for- Schleife einen bedingungslosen Aufruf der
return-Anweisung festgestellt. Möglicherweise ist dies ein Fehler, oder Sie müssen den Code neu schreiben, um die
if-Anweisung zu verwenden .
Warnung 2V6014 Es ist seltsam, dass diese Methode immer ein und denselben Wert zurückgibt. DefaultExceptionHandler.java (40)
@Override public boolean handle(ErrorEvent event, App app) { Throwable t = event.getThrowable(); if (t instanceof SocketException || ExceptionUtils.getRootCause(t) instanceof SocketException) { return true; } if (ExceptionUtils.getThrowableList(t).stream() .anyMatch(o -> o.getClass().getName().equals("...."))) { return true; } if (StringUtils.contains(ExceptionUtils.getMessage(t), "....")) { return true; } AppUI ui = AppUI.getCurrent(); if (ui == null) { return true; } if (t != null) { if (app.getConnection().getSession() != null) { showDialog(app, t); } else { showNotification(app, t); } } return true; }
Diese Funktion gibt in allen Fällen
true zurück . Aber hier in der allerletzten Zeile bittet um die Rückkehr von
falsch . Vielleicht liegt ein Fehler vor.
Die gesamte Liste verdächtiger Funktionen mit ähnlichem Code:
- V6014 Es ist seltsam, dass diese Methode immer ein und denselben Wert zurückgibt. ErrorNodesFinder.java (31)
- V6014 Es ist seltsam, dass diese Methode immer ein und denselben Wert zurückgibt. FileDownloadController.java (69)
- V6014 Es ist seltsam, dass diese Methode immer ein und denselben Wert zurückgibt. IdVarSelector.java (73)
- V6014 Es ist seltsam, dass diese Methode immer ein und denselben Wert zurückgibt. IdVarSelector.java (48)
- V6014 Es ist seltsam, dass diese Methode immer ein und denselben Wert zurückgibt. IdVarSelector.java (67)
- V6014 Es ist seltsam, dass diese Methode immer ein und denselben Wert zurückgibt. IdVarSelector.java (46)
- V6014 Es ist seltsam, dass diese Methode immer ein und denselben Wert zurückgibt. JoinVariableNode.java (57)
Warnung 3V6007 Der Ausdruck 'needReload' ist immer falsch. WebAbstractTable.java (2702)
protected boolean handleSpecificVariables(Map<String, Object> variables) { boolean needReload = false; if (isUsePresentations() && presentations != null) { Presentations p = getPresentations(); if (p.getCurrent() != null && p.isAutoSave(p.getCurrent()) && needUpdatePresentation(variables)) { Element e = p.getSettings(p.getCurrent()); saveSettings(e); p.setSettings(p.getCurrent(), e); } } return needReload; }
Die Funktion gibt die Variable
needReload zurück , deren Wert immer
false ist . Höchstwahrscheinlich haben sie unter einer der Bedingungen vergessen, den Code zum Ändern des Werts der Variablen hinzuzufügen.
Warnung 4V6062 Mögliche unendliche Rekursion innerhalb der 'isFocused'-Methode. GwtAceEditor.java (189), GwtAceEditor.java (190)
public final native void focus() ; public final boolean isFocused() { return this.isFocused(); }
Der Analysator hat eine Funktion erkannt, die rekursiv aufgerufen wird, ohne dass die Rekursion gestoppt werden muss. Diese Datei verfügt über viele Funktionen, die mit dem
nativen Schlüsselwort gekennzeichnet sind und kommentierten Code enthalten. Höchstwahrscheinlich wird die Datei derzeit überschrieben, und bald werden die Entwickler auf die Funktion
isFocused achten .
Verschiedene Warnungen
Warnung 1V6002 Die switch-Anweisung deckt nicht alle Werte der Enum 'Operation' ab: ADD. DesktopAbstractTable.java (665)
enum Operation { REFRESH, CLEAR, ADD, REMOVE, UPDATE } @Override public void setDatasource(final CollectionDatasource datasource) { .... collectionChangeListener = e -> { switch (e.getOperation()) { case CLEAR: case REFRESH: fieldDatasources.clear(); break; case UPDATE: case REMOVE: for (Object entity : e.getItems()) { fieldDatasources.remove(entity); } break; } }; .... }
Die
switch-Anweisung adressiert nicht den Wert von
ADD . Es ist das einzige, das nicht überprüft wurde. Es lohnt sich daher zu prüfen, ob es sich um einen Fehler handelt oder nicht.
Warnung 2V6021 Variable 'Quelle' wird nicht verwendet. DefaultHorizontalLayoutDropHandler.java (177)
@Override protected void handleHTML5Drop(DragAndDropEvent event) { LayoutBoundTransferable transferable = (LayoutBoundTransferable) event .getTransferable(); HorizontalLayoutTargetDetails details = (HorizontalLayoutTargetDetails) event .getTargetDetails(); AbstractOrderedLayout layout = (AbstractOrderedLayout) details .getTarget(); Component source = event.getTransferable().getSourceComponent();
Die Quellvariable wird deklariert und nicht im Code verwendet. Vielleicht haben sie wie eine andere
Comp- Variable des gleichen Typs vergessen, dem
Layout eine
Quelle hinzuzufügen.
Weitere Funktionen mit nicht verwendeten Variablen:
- V6021 Variable 'Quelle' wird nicht verwendet. DefaultHorizontalLayoutDropHandler.java (175)
- V6021 Der Wert wird der Variablen 'r' zugewiesen, aber nicht verwendet. ExcelExporter.java (262)
- V6021 Variable 'over' wird nicht verwendet. DefaultCssLayoutDropHandler.java (49)
- V6021 Variable 'übertragbar' wird nicht verwendet. DefaultHorizontalLayoutDropHandler.java (171)
- V6021 Variable 'übertragbar' wird nicht verwendet. DefaultHorizontalLayoutDropHandler.java (169)
- V6021 Die Variable 'beanLocator' wird nicht verwendet. ScreenEventMixin.java (28)
Warnung 3V6054 Klassen sollten nicht mit ihrem Namen verglichen werden. MessageTools.java (283)
public boolean hasPropertyCaption(MetaProperty property) { Class<?> declaringClass = property.getDeclaringClass(); if (declaringClass == null) return false; String caption = getPropertyCaption(property); int i = caption.indexOf('.'); if (i > 0 && declaringClass.getSimpleName().equals(caption.substring(0, i))) return false; else return true; }
Der Analysator hat eine Situation festgestellt, als der Klassenvergleich namentlich durchgeführt wurde. Ein solcher Vergleich ist falsch, da JVM-Klassen gemäß der Spezifikation nur innerhalb des Pakets einen eindeutigen Namen haben. Dies kann zu einem falschen Vergleich und zur Ausführung des geplanten falschen Codes führen.
Feedback von CUBA Platform-Entwicklern
Natürlich gibt es in jedem größeren Projekt Fehler. Aus diesem Grund haben wir dem Vorschlag des PVS-Studio-Teams zur Überprüfung unseres Projekts gerne zugestimmt. Das CUBA-Repository enthält Gabeln einiger OSS-Bibliotheken von Drittanbietern unter der Apache 2-Lizenz, und es scheint, dass wir diesem Code mehr Aufmerksamkeit schenken müssen. Der Analysator hat in diesen Quellen einige Probleme festgestellt. Jetzt verwenden wir SpotBugs als Hauptanalysator und es werden keine wesentlichen Probleme von PVS-Studio festgestellt. Es ist Zeit, selbst zusätzliche Schecks zu schreiben. Vielen Dank an das PVS-Studio Team für die geleistete Arbeit.Die Entwickler stellten außerdem fest, dass die Warnungen V6013 und V6054 falsch sind. Der Code wurde so absichtlich geschrieben. Ein statischer Analysator erkennt verdächtige Codefragmente und die Wahrscheinlichkeit, Fehler zu finden, ist bei allen Inspektionen unterschiedlich. Trotzdem ist es einfach, mit solchen Warnungen zu arbeiten, indem der praktische Mechanismus zur
Massenunterdrückung von Analysatorwarnungen verwendet wird, ohne die Quellcodedateien zu ändern.
Ein anderes PVS-Studio-Team kann den Satz „Es ist Zeit, selbst zusätzliche Schecks zu schreiben“ nicht ignorieren und dieses Bild nicht verlassen :)
Fazit
PVS-Studio ist eine großartige Ergänzung zu Ihrem bestehenden Projekt, um Tools für die Codequalität zu verbessern. Besonders wenn es Dutzende, Hunderte und Tausende von Mitarbeitern gibt. PVS-Studio wurde entwickelt, um Fehler nicht nur zu finden, sondern auch zu beheben. Darüber hinaus geht es nicht um die automatische Codebearbeitung, sondern um eine zuverlässige Codequalitätskontrolle. In einem großen Unternehmen ist es unmöglich, sich eine Situation vorzustellen, in der absolut alle Entwickler ihren Code unabhängig mit verschiedenen Tools überprüfen. Tools wie PVS-Studio eignen sich daher besser für solche Unternehmen, in denen die Qualitätskontrolle des Codes in allen Entwicklungsphasen bereitgestellt wird, nicht nur für einen normalen Programmierer.

Wenn Sie diesen Artikel einem englischsprachigen Publikum zugänglich machen möchten, verwenden Sie bitte den Link zur Übersetzung: Svyatoslav Razmyslov.
Analysieren des Codes der CUBA-Plattform mit PVS-Studio