انتقل إلى المحتوى الرئيسي

الفصل 5: تصحيح الأخطاء ومراجعة الكود

الهدف

تحاكي هذه التمارين أسئلة المقابلات حيث يتم تقديم كود به أخطاء أو سيء الكتابة ويُطلب منك العثور على المشاكل وتصحيحها. إنه تنسيق شائع جدًا لدى الشركات الكبرى (على غرار Sonar/Codility).


التمرين 1 — العثور على 4 أخطاء وتصحيحها​

"هذا الكود يحتوي على 4 أخطاء. اعثر عليها وصححها."

الكود الذي يحتوي على أخطاء​

public class DiscountCalculator {

public double calculateDiscount(List<Product> products, String coupon) {
double total = 0;
for (int i = 0; i <= products.size(); i++) { // BUG 1
Product p = products.get(i);
if (p.getPrice() > 0) {
total =+ p.getPrice(); // BUG 2
}
}
double discount = 0;
if (coupon == "SAVE20") { // BUG 3
discount = total * 0.20;
}
return total - discount; // BUG 4: pas de null check
}
}

الكود المصحح​

public class DiscountCalculator {

public double calculateDiscount(List<Product> products, String coupon) {
if (products == null || products.isEmpty()) return 0; // FIX 4

double total = 0;
for (int i = 0; i < products.size(); i++) { // FIX 1: < au lieu de <=
Product p = products.get(i);
if (p.getPrice() > 0) {
total += p.getPrice(); // FIX 2: += au lieu de =+
}
}
double discount = 0;
if ("SAVE20".equals(coupon)) { // FIX 3: .equals() au lieu de ==
discount = total * 0.20;
}
return total - discount;
}
}

شرح الأخطاء​

الخطأالمشكلةالنتيجةالتصحيح
1<= بدلاً من <ArrayIndexOutOfBoundsExceptionالتغيير إلى <
2=+ بدلاً من +==+ تعني تعيين +القيمة (موجب)، لا تجمعالتغيير إلى +=
3== يقارن المراجعيمكن أن يكون لسلسلتين متطابقتين مراجع مختلفةاستخدام .equals()
4لا يوجد تحقق من nullNullPointerException إذا كانت products تساوي nullإضافة شرط حماية
الفخاخ المفضلة لدى المقابلين
  • =+ مقابل += : أحدهما يعين، والآخر يجمع. دقيق جدًا بصريًا.
  • == مقابل .equals() : في Java، == يقارن عناوين الذاكرة، وليس المحتوى.
  • خطأ off-by-one (<= مقابل <) : الخطأ الأكثر شيوعًا في البرمجة.
عبارة المقابلة

"I found four bugs: off-by-one error in the loop, assignment instead of addition, reference comparison instead of .equals(), and missing null safety. These are classic Java pitfalls."


التمرين 2 — مراجعة كود كاملة​

"قم بمراجعة هذا الكود. ماذا ستُحسن؟"

الكود المراد مراجعته​

public class DataProcessor {
public static ArrayList<String> process(ArrayList<String> data) {
ArrayList<String> result = new ArrayList<>();
for (int i = 0; i < data.size(); i++) {
String s = data.get(i);
if (s != null) {
if (s.length() > 0) {
if (!s.equals("SKIP")) {
result.add(s.toUpperCase());
}
}
}
}
return result;
}
}

الكود المحسن​

public class DataProcessor {

public List<String> process(List<String> data) {
if (data == null) return List.of();

return data.stream()
.filter(Objects::nonNull)
.filter(s -> !s.isEmpty())
.filter(s -> !"SKIP".equals(s))
.map(String::toUpperCase)
.toList();
}
}

جميع المشاكل المحددة​

#المشكلةالتصحيحالمبدأ
1ArrayList في التوقيعاستخدام List (واجهة)البرمجة ضد الواجهة
2طريقة staticإزالة staticإمكانية الاختبار (mocking)
3لا يوجد شرط حماية لـ nullإضافة if (data == null) return List.of()سلامة null
4if المتداخلة (3 مستويات)Stream pipelineالقابلية للقراءة
5s.equals("SKIP")"SKIP".equals(s)تجنب NPE
6s.length() > 0s.isEmpty()كود اصطلاحي
7ArrayList قابلة للتغيير معادةtoList() يعيد قائمة غير قابلة للتغييرعدم القابلية للتغيير
عبارة المقابلة

"I'd refactor to use streams for readability, program to interfaces, and add null safety. This is exactly the kind of code Sonar would flag — nested conditionals and mutable state."


التمرين 3 — العثور على تسرب الذاكرة​

"هذا الكود يسبب تسربًا في الذاكرة. اعثر عليه."

الكود الإشكالي​

@Service
public class CacheService {
private final Map<String, Object> cache = new HashMap<>();

public void put(String key, Object value) {
cache.put(key, value);
}

public Object get(String key) {
return cache.get(key);
}

// Pas de méthode pour vider le cache !
// Le Map grandit indéfiniment → OutOfMemoryError
}

الكود المصحح​

@Service
public class CacheService {
private final Map<String, Object> cache = new LinkedHashMap<>() {
@Override
protected boolean removeEldestEntry(Map.Entry<String, Object> eldest) {
return size() > 1000; // Limite à 1000 entrées
}
};

// Ou mieux, utiliser Caffeine/Redis
}

الأفضل: استخدام Spring Cache​

@Service
public class ProductService {

@Cacheable(value = "products", key = "#id")
public Product findById(Long id) {
return productRepository.findById(id).orElseThrow();
}

@CacheEvict(value = "products", key = "#id")
public void delete(Long id) {
productRepository.deleteById(id);
}
}
عبارة المقابلة

"A HashMap cache without eviction policy is a memory leak waiting to happen. I'd use Spring Cache with Caffeine or Redis, which handle TTL and max size automatically."


التمرين 4 — العثور على مشكلة التزامن​

"هل هذا الكود آمن للخيوط؟ اعثر على المشكلة."

الكود الإشكالي​

@Service
public class CounterService {
private int count = 0;

public void increment() {
count++; // PAS thread-safe !
}

public int getCount() {
return count;
}
}

المشكلة: count++ ليست ذرية. في الواقع، هي 3 عمليات:

  1. قراءة count
  2. إضافة 1
  3. كتابة القيمة الجديدة

إذا قام خيطان بتنفيذ count++ في نفس الوقت، يمكن فقدان زيادة.

الحلول​

// Solution 1 : AtomicInteger
@Service
public class CounterService {
private final AtomicInteger count = new AtomicInteger(0);

public void increment() {
count.incrementAndGet();
}

public int getCount() {
return count.get();
}
}

// Solution 2 : synchronized
@Service
public class CounterService {
private int count = 0;

public synchronized void increment() {
count++;
}

public synchronized int getCount() {
return count;
}
}
عبارة المقابلة

"count++ is not atomic — it's read-modify-write. I'd use AtomicInteger for lock-free thread safety, or synchronized if I need to protect a larger critical section."


التمرين 5 — تصحيح إدارة الاستثناءات السيئة​

"ما الخطأ في إدارة الاستثناءات هذه؟"

الكود السيء​

@Service
public class PaymentService {

public void processPayment(PaymentRequest req) {
try {
gateway.charge(req.getAmount());
orderService.updateStatus(req.getOrderId(), "PAID");
} catch (Exception e) {
// PROBLÈME 1 : catch trop large
// PROBLÈME 2 : avale l'exception silencieusement
// PROBLÈME 3 : log inutile
System.out.println("Error: " + e.getMessage());
}
}
}

الكود المصحح​

@Service
@Slf4j
public class PaymentService {

@Transactional
public void processPayment(PaymentRequest req) {
try {
gateway.charge(req.getAmount());
} catch (PaymentDeclinedException e) {
log.warn("Payment declined for order {}: {}", req.getOrderId(), e.getMessage());
throw new BusinessException("Payment was declined", e);
} catch (GatewayTimeoutException e) {
log.error("Gateway timeout for order {}", req.getOrderId(), e);
throw new ServiceUnavailableException("Payment gateway is down", e);
}

orderService.updateStatus(req.getOrderId(), "PAID");
}
}

المشاكل المحددة​

#المشكلةلماذا هي خطيرة
1catch (Exception e)يلتقط كل شيء، بما في ذلك NullPointerException, OutOfMemoryError
2استثناء مبلوعالمتصل لا يعرف أبدًا أن الدفع قد فشل
3System.out.printlnلا يوجد سجل منظم، لا يوجد تتبع للمكدس، مفقود في الإنتاج
4لا يوجد تراجعإذا نجح الشحن ولكن فشل updateStatus، بيانات غير متسقة
عبارة المقابلة

"Never catch generic Exception. Catch specific exceptions, log with proper severity (warn vs error), re-throw as business exceptions, and use @Transactional for data consistency."


التمرين 6 — تحديد الروائح في الكود​

"اذكر جميع المشاكل في هذا الكود."

الكود المراد تحليله​

public class Utils {

public static String x(String s) {
String r = "";
for (int i = s.length() - 1; i >= 0; i--) {
r = r + s.charAt(i);
}
return r;
}

public static boolean check(String email) {
if (email.contains("@") == true) {
if (email.contains(".") == true) {
return true;
} else {
return false;
}
} else {
return false;
}
}

public static int calc(int a, int b, String op) {
if (op == "+") return a + b;
if (op == "-") return a - b;
if (op == "*") return a * b;
if (op == "/") return a / b;
return 0;
}
}

المشاكل​

الطريقةالمشاكل
x()اسم غير مفهوم، دمج في حلقة (O(n²))، لا يوجد تحقق من null
check()== true زائدة، if/else return true/false يمكن استبدالها بتعبير
calc()== بدلاً من .equals(), قسمة على الصفر، تعيد 0 بصمت

الكود المصحح​

public class StringUtils {

public static String reverse(String input) {
if (input == null) return null;
return new StringBuilder(input).reverse().toString();
}
}

public class EmailValidator {

public static boolean isValid(String email) {
return email != null && email.contains("@") && email.contains(".");
}
}

public class Calculator {

public static int calculate(int a, int b, String operator) {
return switch (operator) {
case "+" -> a + b;
case "-" -> a - b;
case "*" -> a * b;
case "/" -> {
if (b == 0) throw new ArithmeticException("Division by zero");
yield a / b;
}
default -> throw new IllegalArgumentException("Unknown operator: " + operator);
};
}
}

قائمة مراجعة الكود​

عندما يُطلب منك "مراجعة" الكود في مقابلة، تحقق بشكل منهجي من:

الفئةالنقاط التي يجب التحقق منها
سلامة nullهل هناك تحقق من null عند الإدخال؟ هل تم استخدام Optional؟
التسميةهل الأسماء واضحة؟ هل تتبع اتفاقيات Java (camelCase)؟
الاستثناءاتهل هناك catch محدد؟ هل لا يوجد استثناء مبلوع؟
السلاسلهل تم استخدام .equals() وليس ==؟ هل تم استخدام StringBuilder للدمج في حلقة؟
المجموعاتهل تم استخدام الواجهات (List وليس ArrayList)? هل هي غير قابلة للتغيير عند الإمكان؟
SOLIDهل هناك مسؤولية واحدة؟ هل تعتمد على التجريدات؟
سلامة الخيوطهل المتغيرات المشتركة محمية؟
الأداءلا يوجد N+1؟ لا توجد حلقة O(n²) مخفية؟
الأمانلا يوجد حقن SQL؟ هل تم التحقق من صحة المدخلات؟

⬅️ الفصل 4: REST & Patterns | ⬅️ العودة إلى الوحدة 18