Skip to content

Coding Standards

Türkçe

Bu sayfa ICOSYS kod tabanından çıkarılmış kodlama standartlarını içerir. Tüm yeni kod bu kurallara uymalıdır. SonarQube Grade A hedeflenir.

General Principles

Principle Description
DRY No code duplication — extract to methods/constants
YAGNI Don't build for hypothetical future requirements
Early Return Guard clauses instead of deep nesting
English Only All code, JavaDoc, and comments in English
Backward Compatibility Don't break existing APIs or behavior

Java — Backend Standards

Dependency Injection

Always use constructor injection. Never use field injection.

// WRONG — Field injection (SonarQube violation)
@Inject
private SomeService service;

// CORRECT — Constructor injection
private final SomeService service;

@Inject  // or just single-constructor auto-detection in Spring
public MyClass(SomeService service) {
    this.service = service;
}

Türkçe

Spring Boot'ta tek constructor varsa @Inject/@Autowired bile gerekmez — Spring otomatik algılar. @RequiredArgsConstructor ile Lombok bunu daha da kısaltır.

For controllers, prefer Lombok:

@RestController
@RequestMapping("/api/branch")
@RequiredArgsConstructor
public class BranchController {
    private final BranchService service;  // auto-injected
}

Controller Pattern

@RestController
@RequestMapping("/api/{entity}")
@RequiredArgsConstructor
public class MyEntityController {

    private final MyEntityService service;

    @GetMapping("/{id}")
    public ResponseEntity<MyEntityEditDto> findById(@PathVariable Long id) {
        return service.findById(id)
                .map(ResponseEntity::ok)
                .orElse(ResponseEntity.notFound().build());
    }

    @PostMapping
    public ResponseEntity<MyEntityEditDto> create(@RequestBody MyEntityEditDto dto) {
        return ResponseEntity.ok(service.create(dto));
    }

    @PutMapping("/{id}")
    public ResponseEntity<MyEntityEditDto> update(@PathVariable Long id,
                                                   @RequestBody MyEntityEditDto dto) {
        return ResponseEntity.ok(service.update(id, dto));
    }

    @DeleteMapping("/{id}")
    public ResponseEntity<Void> delete(@PathVariable Long id) {
        service.delete(id);
        return ResponseEntity.noContent().build();
    }

    @PostMapping("/list")
    public ResponseEntity<PageResponse<MyEntityListDto>> findPaged(
            @RequestBody PageRequest<MyEntityFilterDto> request) {
        return ResponseEntity.ok(service.findPaged(request));
    }

    @GetMapping("/stats")
    public ResponseEntity<EntityStatsDto> getStats() {
        return ResponseEntity.ok(service.getStats());
    }

    // Custom endpoints AFTER standard CRUD
}

Türkçe

Standart CRUD endpoint'leri her controller'da aynı sırayla yazılır: findByIdcreateupdatedeletefindPagedgetStats → özel endpoint'ler.

Service Pattern

@Service
@Transactional
@Slf4j
public class MyEntityService extends AbstractCrudService<
        MyEntity, Long, MyEntityEditDto, MyEntityListDto, MyEntityFilterDto> {

    // 5 abstract methods MUST be implemented
    @Override protected ICrudRepository<MyEntity, Long> getRepository() { return repository; }
    @Override protected IEditDtoConverter<MyEntity, MyEntityEditDto> getEditConverter() { return editConverter; }
    @Override protected IListDtoConverter<MyEntity, MyEntityListDto> getListConverter() { return listConverter; }
    @Override protected String getEntityName() { return "MyEntity"; }
    @Override protected Long extractId(MyEntity entity) { return entity.getId(); }

    // Lifecycle hooks — override as needed
    @Override protected void validateForCreate(MyEntityEditDto dto) { }
    @Override protected void beforeCreate(MyEntity entity, MyEntityEditDto dto) { }
}

Transaction Rules

Rule Reason
@Transactional only on public methods Spring proxy cannot intercept private/protected
Use readOnly = true for queries Enables Hibernate optimizations
Never call a @Transactional method from the same class Self-invocation bypasses the proxy

Self-invocation fix:

// WRONG — self-invocation, @Transactional is bypassed
public void process() {
    saveData();  // calls this.saveData(), NOT the proxy
}

@Transactional
public void saveData() { }

// CORRECT — extract shared logic to private helper
private void saveInternal() {
    // shared logic here
}

@Transactional
public void process() {
    saveInternal();  // private, no proxy needed
}

@Transactional
public void saveData() {
    saveInternal();
}

Exception Handling

// WRONG
catch (Exception e) {
    e.printStackTrace();
}

// CORRECT — specific exception + error code + SLF4J
catch (DataAccessException e) {
    log.error("Failed to save entity: {}", e.getMessage(), e);
    throw ServiceException.of(ErrorCodes.SYS_DATA_ACCESS_ERROR,
            ServiceErrorMsgCodes.ERROR_SAVE_FAILED, entityName);
}

Rules:

  • Never catch generic Exception — use specific exception types
  • Never use e.printStackTrace() — use SLF4J
  • Always use ErrorCodes constants — never raw strings in ServiceException
  • Always include the original exception as cause

Logging

// WRONG
System.out.println("Debug: " + value);
log.debug("Value: " + expensiveCall());

// CORRECT
log.debug("REST request to get entity by id: {}", id);
log.error("Operation failed for entity {}: {}", entityName, e.getMessage(), e);

// For expensive operations
if (log.isDebugEnabled()) {
    log.debug("Detailed state: {}", computeExpensiveDebugInfo());
}

Null Safety

// WRONG
if (obj.getValue() != null) { }

// CORRECT — null-safe chain
if (obj != null && obj.getValue() != null) { }

// BEST — Optional
Optional.ofNullable(obj)
    .map(Obj::getValue)
    .ifPresent(this::process);

Constants

// WRONG — magic strings and numbers
if (retryCount > 3) { }
predicates.add(cb.equal(root.get("crProcess").get("id"), crProcessId));

// CORRECT — named constants
private static final int MAX_RETRY_COUNT = 3;
private static final String FIELD_ID = "id";
private static final String REL_CR_PROCESS = "crProcess";

if (retryCount > MAX_RETRY_COUNT) { }
predicates.add(cb.equal(root.get(REL_CR_PROCESS).get(FIELD_ID), crProcessId));

String Comparison

// WRONG
if (status == "ACTIVE") { }
if (str.equals("constant")) { }

// CORRECT — constant on left (null-safe)
if ("ACTIVE".equals(status)) { }

Resource Management

// WRONG
InputStream is = new FileInputStream(file);
// ... use is
is.close();

// CORRECT — try-with-resources
try (InputStream is = new FileInputStream(file)) {
    // ... use is
}

Cognitive Complexity

// WRONG — deeply nested
public void process(Data data) {
    if (data != null) {
        if (data.isValid()) {
            if (data.getType() == Type.A) {
                // ...
            }
        }
    }
}

// CORRECT — early return / guard clauses
public void process(Data data) {
    if (data == null || !data.isValid()) {
        return;
    }
    if (data.getType() == Type.A) {
        processTypeA(data);
        return;
    }
    processDefault(data);
}

Method Parameter Limit

SonarQube flags methods with more than 7 parameters. Bundle into a Request DTO:

// WRONG — 10 parameters
public Response upload(MultipartFile file, Long crProcessId, String entityType,
        Long entityId, String key, String category, String desc,
        String tags, String ip, String userAgent) { }

// CORRECT — Request DTO
public Response upload(MultipartFile file, UploadRequest request,
        String ipAddress, String userAgent) { }

Dead Code

  • Remove unused imports, variables, and methods
  • Delete commented-out code immediately — it lives in Git history
  • No // TODO without a linked ticket

TypeScript — Frontend Standards

Component Structure

// Functional component with TypeScript props
interface MyComponentProps {
    entityId: number;
    onSave: (data: FormData) => void;
}

export function MyComponent({ entityId, onSave }: MyComponentProps) {
    // 1. Hooks (useState, useEffect, custom hooks)
    const { user } = useAuth();
    const [loading, setLoading] = useState(false);

    // 2. Derived state (useMemo, useCallback)
    const canEdit = useMemo(() => user.hasRole('EDIT'), [user]);

    // 3. Event handlers
    const handleSubmit = useCallback((data: FormData) => {
        onSave(data);
    }, [onSave]);

    // 4. Render
    return <div>...</div>;
}

State Management

Scope Tool Example
Component-local useState Form input values
Derived/computed useMemo / useCallback Filtered lists, memoized handlers
Global (app-wide) Zustand store Auth state, navigation stack
Server state Axios + custom hooks API data fetching
// Zustand store pattern
export const useAuthStore = create<AuthState>((set) => ({
    user: null,
    isAuthenticated: false,
    login: async (credentials) => { /* ... */ },
    logout: () => set({ user: null, isAuthenticated: false }),
}));

API Client Pattern

// Centralized Axios instance
const apiClient = axios.create({
    baseURL: import.meta.env.VITE_ICGLB_URL,
    withCredentials: true,
});

// Response interceptor for global error handling
apiClient.interceptors.response.use(
    (response) => response,
    (error) => {
        if (error.response?.status === 401) {
            // redirect to login
        }
        return Promise.reject(ApiError.from(error));
    }
);

Form Handling

// React Hook Form + Zod validation
const schema = z.object({
    name: z.string().min(1, 'Required'),
    email: z.string().email('Invalid email'),
});

const { register, handleSubmit, formState: { errors } } = useForm({
    resolver: zodResolver(schema),
});

Styling

// Use cn() utility for conditional classes
import { cn } from '@/shared/utils/cn';

className={cn(
    "px-4 py-2 rounded-md",
    isActive && "bg-primary-500 text-white",
    isDisabled && "opacity-50 cursor-not-allowed"
)}

Türkçe

cn() fonksiyonu clsx + tailwind-merge birleşimidir. Tailwind class çakışmalarını otomatik çözer (örn. px-2 ile px-4 varsa sadece px-4 kalır).


SonarQube Compliance

Target: Grade A on all projects.

Auto-Fix Rules (Fix immediately, no confirmation needed)

Issue Action
Field injection Convert to constructor injection
Missing JavaDoc Add English JavaDoc to public methods
Code smells Fix immediately
Duplicate code Extract to shared method or constant
Security hotspots Fix immediately
Commented-out code Delete
Unused imports/variables Remove

SonarQube Configuration

Setting Value
Coverage exclusions **/* (Entity/DTO jars excluded)
Duplication exclusions **/*Dto.java, **/entities/*.java
Scanner plugin sonar-maven-plugin 4.0–5.0
# Run SonarQube analysis
mvn clean verify sonar:sonar -DskipTests -Dsonar.projectKey=PROJECT_NAME