Java-Entwickler haben Zugriff auf eine Reihe nützlicher Tools, mit denen qualitativ hochwertiger Code geschrieben werden kann, z. B. die leistungsstarke IDE IntelliJ IDEA, die kostenlosen Analysatoren SpotBugs, PMD und dergleichen. Die Entwickler, die an der CUBA-Plattform arbeiten, haben bereits alle diese Funktionen verwendet. Diese Überprüfung wird zeigen, wie das Projekt noch mehr von der Verwendung des statischen Code-Analysators PVS-Studio profitieren kann.
Ein paar Worte zum Projekt und zum Analysator
Die CUBA-Plattform ist ein
übergeordnetes Framework für die Entwicklung von Unternehmensanwendungen. Die Plattform abstrahiert Entwickler von zugrunde liegenden Technologien, damit sie sich auf die Geschäftsaufgaben konzentrieren können und gleichzeitig die volle Flexibilität behalten, indem sie uneingeschränkten Zugriff auf Code auf niedriger Ebene gewähren. Der Quellcode wurde von
GitHub heruntergeladen.
PVS-Studio ist ein Tool zum Erkennen von Fehlern und potenziellen Sicherheitslücken im Quellcode von Programmen, die in C, C ++, C # und Java geschrieben wurden. Der Analysator läuft auf 64-Bit-Windows-, Linux- und MacOS-Systemen. Um Java-Programmierern die Arbeit zu erleichtern, haben wir Plugins für Maven, Gradle und IntelliJ IDEA entwickelt. Ich habe das Projekt mit dem Gradle-Plugin überprüft und es lief reibungslos ab.
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)); } .... }
Der aus dem Element extrahierte Attributwert wird nicht überprüft. Stattdessen erhält die Funktion
isNotEmpty anstelle der Variablen
handleTabKey ein Zeichenfolgenliteral als Argument.
Ein ähnlicher Fehler wurde in der Datei AbstractTableLoader.java gefunden:
- 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 gibt
-1 zurück, wenn das Element nicht in der Liste gefunden wird. Der Wert
1 wird dann zum Index hinzugefügt, wodurch das Problem mit dem fehlenden Element verschleiert wird. Ein weiteres potenzielles Problem hat damit zu tun, dass die Variable
previousMenuItemFlatIndex immer größer oder gleich Null ist. Wenn beispielsweise
festgestellt wird, dass die Liste
menuItemWidgets leer ist, führt das Programm zu einem Array-Überlauf.
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 "); } .... }
Die letzten beiden Zeichen des
orderBy- Puffers werden gelöscht, wenn die Gesamtzahl der Elemente größer als Null ist, d. H. Wenn die Zeichenfolge mindestens ein Zeichen enthält. Die Startposition, an der der Löschvorgang beginnt, wird jedoch um 2 versetzt. Wenn
orderBy also zufällig ein Zeichen enthält, wird beim Versuch, es zu löschen, eine
StringIndexOutOfBoundsException ausgelöst .
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 die Werte von
Entitäten in
masterCollection kopiert. Eine Zeile zuvor wurden die Sammlungen anhand von Referenzen verglichen, aber der Programmierer beabsichtigte wahrscheinlich, dass es sich um einen Vergleich nach Wert handelt.
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; }
Dieser Fall ähnelt dem vorherigen. Die Sammlungen werden in der Funktion
isCollectionValuesChanged verglichen, und der Referenzvergleich ist möglicherweise auch hier nicht beabsichtigt.
Redundante Bedingungen
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)) {
Die zweite Bedingung prüft einen Ausdruck, der dem in der ersten Bedingung geprüften Ausdruck entgegengesetzt ist. Letzteres kann daher sicher entfernt werden, um den Code zu verkürzen.
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 dem Verlassen der
while- Schleife ist der Wert der
Connector- Variablen nicht gleich
null , sodass die redundante Prüfung gelöscht werden kann.
Eine weitere verdächtige Warnung dieser Art, die untersucht werden muss:
- 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 die Ausführung des Aufrufs von
tx1.commit verhindert . Diese beiden Zeilen sollten vertauscht werden, damit der Code ordnungsgemäß funktioniert.
Auch bei anderen Tests gab es einige ähnliche Probleme:
- 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 Argumente
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 Datenzeichenfolge, die Sie zusammen mit dem Kennwort an eine Hash-Funktion übergeben. Es wird hauptsächlich verwendet, um das Programm vor Wörterbuchangriffen und Regenbogentabellenangriffen zu schützen und identische Passwörter zu verschleiern. Mehr hier:
Salz (Kryptographie) .
In dieser Funktion wird die übergebene Zeichenfolge direkt nach der Eingabe überschrieben. Das Ignorieren des an die Funktion übergebenen Werts ist eine potenzielle Sicherheitsanfälligkeit.
Warnung 2Diese Funktion löste 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); }
Das ist ein merkwürdiger Ausschnitt. Die Funktion wird mit nur zwei Variablen als Argumente
aufgerufen ,
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); }
Die Funktion verwendet den als
Verknüpfungsparameter übergebenen Wert nicht. Möglicherweise ist die Benutzeroberfläche der Funktion veraltet, oder diese Warnung ist nur falsch positiv.
Noch ein paar Mängel dieses Typs:
- 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)
Unterschiedliche Funktionen, gleicher 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 die gleichen Implementierungen. Letzteres sollte wahrscheinlich den Index des letzten Elements abrufen, anstatt das Element auf Index 0 abzurufen.
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 identischen Körpern. Ich vermute, dass einer von ihnen mit einer anderen Farbe anstelle von
color53 arbeiten sollte .
Null-Dereferenzierung
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 Programmierer, einen höchst verdächtigen Code zu schreiben. Zuerst wird die Methode
setPopupPosition der ObjektbeschreibungPopup aufgerufen, und dann wird das Objekt auf
null geprüft. Der erste Aufruf von
setPopupPosition ist wahrscheinlich redundant und möglicherweise gefährlich. Ich denke, es resultiert aus schlechtem Refactoring.
Warnung 2V6060 Die 'tableModel'-Referenz wurde verwendet, bevor sie gegen null verifiziert wurde. DesktopAbstractTable.java (1580), DesktopAbstractTable.java (1564)
protected Column addRuntimeGeneratedColumn(String columnId) {
Dieser Fall ähnelt dem vorherigen. Zu dem Zeitpunkt, an dem das
tableModel- Objekt auf
null geprüft wird, wurde bereits mehrmals darauf zugegriffen.
Ein weiteres Beispiel:
- V6060 Die 'tableModel'-Referenz wurde verwendet, bevor sie gegen null verifiziert wurde. DesktopAbstractTable.java (596), DesktopAbstractTable.java (579)
Wahrscheinlich 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 wurde der Variablen
sortAscending bereits der Wert
true zugewiesen, aber später wird ihr wieder derselbe Wert zugewiesen. Dies muss ein Fehler sein, und der Autor meinte wahrscheinlich den Wert
falsch .
Ein ähnliches Beispiel aus einer anderen Datei:
- V6026 Dieser Wert ist bereits der Variablen 'sortAscending' zugeordnet. CubaTreeTableWidget.java (444)
Seltsame Rückgabewerte
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 einen bedingungslosen Aufruf zur
Rückkehr bei der ersten Iteration einer
for- Schleife erkannt. Entweder ist diese Zeile falsch oder die Schleife sollte als
if- Anweisung neu geschrieben werden.
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 jeweils
true zurück, während die letzte Zeile offensichtlich
false fordert. Es sieht aus wie ein Fehler.
Hier ist eine vollständige Liste anderer ähnlicher verdächtiger Funktionen:
- 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
falsch ist . In einer der Bedingungen fehlt wahrscheinlich ein Code zum Ändern dieses Werts.
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 rekursive Funktion ohne Stoppbedingung erkannt. Diese Datei enthält viele Funktionen, die mit dem Schlüsselwort
native gekennzeichnet sind und auskommentierten Code enthalten. Die Entwickler schreiben diese Datei wahrscheinlich gerade neu und werden bald auch die Funktion
isFocused bemerken.
Verschiedenes
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 hat keine Groß- / Kleinschreibung für den Wert
ADD . Dies ist der einzige Wert, der nicht überprüft wird. Daher sollten sich die Entwickler diesen Code ansehen.
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 Variablenquelle wird deklariert, aber nicht verwendet. Vielleicht haben die Autoren vergessen, dem
Layout eine
Quelle hinzuzufügen, genau wie bei einer anderen Variablen dieses Typs,
comp .
Andere 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 einen Klassenvergleich mit Namen festgestellt. Es ist falsch, Klassen nach Namen zu vergleichen, da die Namen von JVM-Klassen gemäß der Spezifikation nur innerhalb eines Pakets eindeutig sein dürfen. Ein solcher Vergleich führt zu falschen Ergebnissen und zur Ausführung des falschen Codes.
Kommentar von CUBA Platform-Entwicklern
Jedes große Projekt hat sicherlich Fehler. In diesem Wissen sind wir uns gerne einig, als das PVS-Studio-Team angeboten hat, unser Projekt zu überprüfen. Das CUBA-Repository enthält Gabeln einiger OSS-Bibliotheken von Drittanbietern, die unter Apache 2 lizenziert sind, und es sieht so aus, als sollten wir diesem Code mehr Aufmerksamkeit schenken, da der Analysator in diesen Quellen eine Reihe von Problemen festgestellt hat. Wir verwenden derzeit SpotBugs als primären Analysator, und einige der von PVS-Studio gemeldeten großen Fehler werden nicht bemerkt. Es scheint, wir sollten selbst einige zusätzliche Diagnosen schreiben. Vielen Dank an das PVS-Studio Team für den Job.Die Entwickler sagten uns auch, dass die Warnungen V6013 und V6054 falsch positiv waren; Es war ihre bewusste Entscheidung, diesen Code so zu schreiben, wie sie es taten. Der Analysator wurde zum Erkennen verdächtiger Codefragmente entwickelt, und die Wahrscheinlichkeit, echte Fehler zu finden, variiert je nach Diagnose. Solche Warnungen können jedoch mithilfe des speziellen Mechanismus zur
Unterdrückung von Massenwarnungen problemlos verarbeitet werden, ohne dass die Quelldateien geändert werden müssen.
Außerdem kann das PVS-Studio-Team den Satz "Es scheint, wir sollten selbst zusätzliche Diagnosen schreiben" nicht zur Kenntnis nehmen und auf dieses Bild verzichten :)
Fazit
PVS-Studio kann eine perfekte Ergänzung zu vorhandenen Qualitätskontrollwerkzeugen sein, die in Ihrem Entwicklungsprozess verwendet werden. Dies gilt insbesondere für Unternehmen mit Dutzenden, Hunderten oder Tausenden von Entwicklern. PVS-Studio wurde entwickelt, um Fehler nicht nur zu erkennen, sondern Ihnen auch bei der Behebung zu helfen. Damit meine ich nicht die automatische Codebearbeitung, sondern zuverlässige Mittel zur Kontrolle der Codequalität. In einem großen Unternehmen ist es nicht für jeden Entwickler möglich, die jeweiligen Teile des Codes mit verschiedenen Tools zu überprüfen. Eine bessere Lösung für solche Unternehmen wäre daher die Verwendung von Tools wie PVS-Studio, die in jeder Entwicklungsphase eine Kontrolle der Codequalität ermöglichen nur auf der Programmierseite.